diff --git a/app/src/androidTest/java/at/bitfire/davdroid/HttpClientTest.java b/app/src/androidTest/java/at/bitfire/davdroid/HttpClientTest.java index b1138ac0..afe93560 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/HttpClientTest.java +++ b/app/src/androidTest/java/at/bitfire/davdroid/HttpClientTest.java @@ -26,7 +26,7 @@ public class HttpClientTest extends InstrumentationTestCase { @Override public void setUp() throws IOException { - httpClient = HttpClient.create(getInstrumentation().getTargetContext().getApplicationContext(), null); + httpClient = HttpClient.create(); server = new MockWebServer(); server.start(); diff --git a/app/src/main/java/at/bitfire/davdroid/AccountSettings.java b/app/src/main/java/at/bitfire/davdroid/AccountSettings.java index 2bd61e12..04b23fe7 100644 --- a/app/src/main/java/at/bitfire/davdroid/AccountSettings.java +++ b/app/src/main/java/at/bitfire/davdroid/AccountSettings.java @@ -39,7 +39,9 @@ import java.util.Set; import java.util.logging.Level; import at.bitfire.davdroid.model.ServiceDB; -import at.bitfire.davdroid.model.ServiceDB.*; +import at.bitfire.davdroid.model.ServiceDB.Collections; +import at.bitfire.davdroid.model.ServiceDB.HomeSets; +import at.bitfire.davdroid.model.ServiceDB.Services; import at.bitfire.davdroid.resource.LocalAddressBook; import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalTaskList; @@ -50,12 +52,12 @@ import lombok.Cleanup; import okhttp3.HttpUrl; public class AccountSettings { - private final static int CURRENT_VERSION = 3; - private final static String - KEY_SETTINGS_VERSION = "version", + private final static int CURRENT_VERSION = 3; + private final static String + KEY_SETTINGS_VERSION = "version", KEY_USERNAME = "user_name", - KEY_AUTH_PREEMPTIVE = "auth_preemptive"; + KEY_AUTH_PREEMPTIVE = "auth_preemptive"; /** Time range limitation to the past [in days] value = null default value (DEFAULT_TIME_RANGE_PAST_DAYS) @@ -65,28 +67,32 @@ public class AccountSettings { private final static String KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days"; private final static int DEFAULT_TIME_RANGE_PAST_DAYS = 90; - public final static long SYNC_INTERVAL_MANUALLY = -1; + public final static long SYNC_INTERVAL_MANUALLY = -1; - final Context context; - final AccountManager accountManager; - final Account account; - - - public AccountSettings(@NonNull Context context, @NonNull Account account) { - this.context = context; - this.account = account; - - accountManager = AccountManager.get(context); - - synchronized(AccountSettings.class) { - int version = 0; - try { - version = Integer.parseInt(accountManager.getUserData(account, KEY_SETTINGS_VERSION)); - } catch(NumberFormatException ignored) { - } + final Context context; + final AccountManager accountManager; + final Account account; + + + public AccountSettings(@NonNull Context context, @NonNull Account account) throws InvalidAccountException { + this.context = context; + this.account = account; + + accountManager = AccountManager.get(context); + + synchronized(AccountSettings.class) { + String versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION); + if (versionStr == null) + throw new InvalidAccountException(account); + + int version = 0; + try { + version = Integer.parseInt(versionStr); + } catch (NumberFormatException ignored) { + } App.log.info("Account " + account.name + " has version " + version + ", current version: " + CURRENT_VERSION); - if (version < CURRENT_VERSION) { + if (version < CURRENT_VERSION) { Notification notify = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_new_releases_light) .setLargeIcon(((BitmapDrawable)context.getResources().getDrawable(R.drawable.ic_launcher)).getBitmap()) @@ -104,54 +110,54 @@ public class AccountSettings { update(version); } - } - } + } + } - 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, userName); - bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); - return bundle; - } - - - // authentication settings - - public String username() { return accountManager.getUserData(account, KEY_USERNAME); } - public void username(@NonNull String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); } - - public String password() { return accountManager.getPassword(account); } - public void password(@NonNull String password) { accountManager.setPassword(account, password); } - - public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); } - public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); } + 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, userName); + bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); + return bundle; + } - // sync. settings + // authentication settings - public Long getSyncInterval(@NonNull String authority) { - if (ContentResolver.getIsSyncable(account, authority) <= 0) - return null; + public String username() { return accountManager.getUserData(account, KEY_USERNAME); } + public void username(@NonNull String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); } - if (ContentResolver.getSyncAutomatically(account, authority)) { - List syncs = ContentResolver.getPeriodicSyncs(account, authority); - if (syncs.isEmpty()) - return SYNC_INTERVAL_MANUALLY; - else - return syncs.get(0).period; - } else - return SYNC_INTERVAL_MANUALLY; - } + public String password() { return accountManager.getPassword(account); } + public void password(@NonNull String password) { accountManager.setPassword(account, password); } - public void setSyncInterval(@NonNull String authority, long seconds) { - if (seconds == SYNC_INTERVAL_MANUALLY) { - ContentResolver.setSyncAutomatically(account, authority, false); - } else { - ContentResolver.setSyncAutomatically(account, authority, true); - ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds); - } - } + public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); } + public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); } + + + // sync. settings + + public Long getSyncInterval(@NonNull String authority) { + if (ContentResolver.getIsSyncable(account, authority) <= 0) + return null; + + if (ContentResolver.getSyncAutomatically(account, authority)) { + List syncs = ContentResolver.getPeriodicSyncs(account, authority); + if (syncs.isEmpty()) + return SYNC_INTERVAL_MANUALLY; + else + return syncs.get(0).period; + } else + return SYNC_INTERVAL_MANUALLY; + } + + public void setSyncInterval(@NonNull String authority, long seconds) { + if (seconds == SYNC_INTERVAL_MANUALLY) { + ContentResolver.setSyncAutomatically(account, authority, false); + } else { + ContentResolver.setSyncAutomatically(account, authority, true); + ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds); + } + } public Integer getTimeRangePastDays() { String strDays = accountManager.getUserData(account, KEY_TIME_RANGE_PAST_DAYS); @@ -166,11 +172,11 @@ public class AccountSettings { accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, String.valueOf(days == null ? -1 : days)); } - - // update from previous account settings - - private void update(int fromVersion) { - for (int toVersion = fromVersion + 1; toVersion <= CURRENT_VERSION; toVersion++) { + + // update from previous account settings + + private void update(int fromVersion) { + for (int toVersion = fromVersion + 1; toVersion <= CURRENT_VERSION; toVersion++) { App.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion); try { Method updateProc = getClass().getDeclaredMethod("update_" + fromVersion + "_" + toVersion); @@ -180,51 +186,7 @@ public class AccountSettings { } fromVersion = toVersion; } - } - - @SuppressWarnings({ "Recycle", "unused" }) - private void update_0_1() throws URISyntaxException { - String v0_principalURL = accountManager.getUserData(account, "principal_url"), - v0_addressBookPath = accountManager.getUserData(account, "addressbook_path"); - App.log.fine("Old principal URL = " + v0_principalURL); - App.log.fine("Old address book path = " + v0_addressBookPath); - - URI principalURI = new URI(v0_principalURL); - - // update address book - if (v0_addressBookPath != null) { - String addressBookURL = principalURI.resolve(v0_addressBookPath).toASCIIString(); - App.log.fine("New address book URL = " + addressBookURL); - accountManager.setUserData(account, "addressbook_url", addressBookURL); - } - - // update calendars - ContentResolver resolver = context.getContentResolver(); - Uri calendars = Calendars.CONTENT_URI.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true").build(); - @Cleanup Cursor cursor = resolver.query(calendars, new String[] { Calendars._ID, Calendars.NAME }, null, null, null); - while (cursor != null && cursor.moveToNext()) { - int id = cursor.getInt(0); - String v0_path = cursor.getString(1), - v1_url = principalURI.resolve(v0_path).toASCIIString(); - App.log.fine("Updating calendar #" + id + " name: " + v0_path + " -> " + v1_url); - Uri calendar = ContentUris.appendId(Calendars.CONTENT_URI.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true"), id).build(); - ContentValues newValues = new ContentValues(1); - newValues.put(Calendars.NAME, v1_url); - if (resolver.update(calendar, newValues, null, null) != 1) - App.log.fine("Number of modified calendars != 1"); - } - - accountManager.setUserData(account, "principal_url", null); - accountManager.setUserData(account, "addressbook_path", null); - - accountManager.setUserData(account, KEY_SETTINGS_VERSION, "1"); - } + } @SuppressWarnings({ "Recycle", "unused" }) private void update_1_2() throws ContactsStorageException { diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.java b/app/src/main/java/at/bitfire/davdroid/DavService.java index 47bff126..8b1eaee9 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.java +++ b/app/src/main/java/at/bitfire/davdroid/DavService.java @@ -202,7 +202,7 @@ public class DavService extends Service { DavResource group = new DavResource(httpClient, dav.location.resolve(href)); try { queryHomeSets(serviceType, group, homeSets); - } catch (HttpException|DavException e) { + } catch(HttpException|DavException e) { App.log.log(Level.WARNING, "Couldn't query member group ", e); } } @@ -217,7 +217,7 @@ public class DavService extends Service { if (info.selected) selectedCollections.add(HttpUrl.parse(info.url)); - for (Iterator iterator = homeSets.iterator(); iterator.hasNext();) { + for (Iterator iterator = homeSets.iterator(); iterator.hasNext(); ) { HttpUrl homeSet = iterator.next(); App.log.fine("Listing home set " + homeSet); @@ -241,7 +241,7 @@ public class DavService extends Service { } // check/refresh unconfirmed collections - for (Iterator> iterator = collections.entrySet().iterator(); iterator.hasNext();) { + for (Iterator> iterator = collections.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); HttpUrl url = entry.getKey(); CollectionInfo info = entry.getValue(); @@ -282,7 +282,9 @@ public class DavService extends Service { db.endTransaction(); } - } catch (IOException|HttpException|DavException e) { + } catch(InvalidAccountException e) { + App.log.log(Level.SEVERE, "Invalid account", e); + } catch(IOException|HttpException|DavException e) { App.log.log(Level.SEVERE, "Couldn't refresh collection list", e); Intent debugIntent = new Intent(DavService.this, DebugInfoActivity.class); @@ -387,7 +389,7 @@ public class DavService extends Service { } private void saveCollections(Iterable collections) { - db.delete(Collections._TABLE, HomeSets.SERVICE_ID + "=?", new String[]{String.valueOf(service)}); + db.delete(Collections._TABLE, HomeSets.SERVICE_ID + "=?", new String[] { String.valueOf(service) }); for (CollectionInfo collection : collections) { ContentValues values = collection.toDB(); App.log.log(Level.FINE, "Saving collection", values); diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.java b/app/src/main/java/at/bitfire/davdroid/HttpClient.java index 07cc4eff..138bc5fc 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.java +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.java @@ -43,7 +43,33 @@ public class HttpClient { private HttpClient() { } - public static OkHttpClient create(Context context, Account account, @NonNull final Logger logger) { + public static OkHttpClient create(@NonNull Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException { + OkHttpClient.Builder builder = defaultBuilder(logger); + + // use account settings for authentication and logging + AccountSettings settings = new AccountSettings(context, account); + + if (settings.preemptiveAuth()) + builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(settings.username(), settings.password())); + else + builder.authenticator(new BasicDigestAuthenticator(null, settings.username(), settings.password())); + + return builder.build(); + } + + public static OkHttpClient create(@NonNull Logger logger) { + return defaultBuilder(logger).build(); + } + + public static OkHttpClient create(@NonNull Context context, @NonNull Account account) throws InvalidAccountException { + return create(context, account, App.log); + } + + public static OkHttpClient create() { + return create(App.log); + } + + private static OkHttpClient.Builder defaultBuilder(@NonNull final Logger logger) { OkHttpClient.Builder builder = client.newBuilder(); // use MemorizingTrustManager to manage self-signed certificates @@ -66,16 +92,7 @@ public class HttpClient { // add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking) builder.cookieJar(MemoryCookieStore.INSTANCE); - if (context != null && account != null) { - // use account settings for authentication and logging - AccountSettings settings = new AccountSettings(context, account); - - if (settings.preemptiveAuth()) - builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(settings.username(), settings.password())); - else - builder.authenticator(new BasicDigestAuthenticator(null, settings.username(), settings.password())); - } - + // add network logging, if requested if (logger.isLoggable(Level.FINEST)) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override @@ -87,11 +104,7 @@ public class HttpClient { builder.addInterceptor(loggingInterceptor); } - return builder.build(); - } - - public static OkHttpClient create(Context context, Account account) { - return create(context, account, App.log); + return builder; } private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @NonNull String username, @NonNull String password, boolean preemptive) { diff --git a/app/src/main/java/at/bitfire/davdroid/InvalidAccountException.java b/app/src/main/java/at/bitfire/davdroid/InvalidAccountException.java new file mode 100644 index 00000000..ebc8531f --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/InvalidAccountException.java @@ -0,0 +1,19 @@ +/* + * 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; + +import android.accounts.Account; + +public class InvalidAccountException extends Exception { + + public InvalidAccountException(Account account) { + super("Invalid account: " + account); + } + +} diff --git a/app/src/main/java/at/bitfire/davdroid/log/PlainTextFormatter.java b/app/src/main/java/at/bitfire/davdroid/log/PlainTextFormatter.java index 1d704bd7..ede2d992 100644 --- a/app/src/main/java/at/bitfire/davdroid/log/PlainTextFormatter.java +++ b/app/src/main/java/at/bitfire/davdroid/log/PlainTextFormatter.java @@ -40,16 +40,14 @@ public class PlainTextFormatter extends Formatter { builder.append(String.format("[%s] %s", shortClassName(r.getSourceClassName()), r.getMessage())); - if (r.getThrown() != null) { - Throwable thrown = r.getThrown(); - builder.append("\nEXCEPTION ").append(ExceptionUtils.getMessage(thrown)); - builder.append("\tstrack trace = ").append(ExceptionUtils.getStackTrace(thrown)); - } + if (r.getThrown() != null) + builder .append("\nEXCEPTION ") + .append(ExceptionUtils.getStackTrace(r.getThrown())); if (r.getParameters() != null) { int idx = 1; for (Object param : r.getParameters()) - builder.append("\n\tPARAMETER #").append(idx).append(" = ").append(param); + builder.append("\n\tPARAMETER #").append(idx++).append(" = ").append(param); } if (!logcat) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java index e8d0d7ff..f8bb2a99 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java @@ -43,6 +43,7 @@ import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.App; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalEvent; @@ -62,7 +63,7 @@ public class CalendarSyncManager extends SyncManager { protected static final int MAX_MULTIGET = 20; - public CalendarSyncManager(Context context, Account account, Bundle extras, String authority, SyncResult result, LocalCalendar calendar) { + public CalendarSyncManager(Context context, Account account, Bundle extras, String authority, SyncResult result, LocalCalendar calendar) throws InvalidAccountException { super(Constants.NOTIFICATION_CALENDAR_SYNC, context, account, extras, authority, result); localCollection = calendar; } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java index 47a6fa55..ba0c8c82 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.logging.Level; import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB.Collections; import at.bitfire.davdroid.model.ServiceDB.OpenHelper; @@ -64,6 +65,9 @@ public class CalendarsSyncAdapterService extends SyncAdapterService { } } catch (CalendarStorageException e) { App.log.log(Level.SEVERE, "Couldn't enumerate local calendars", e); + syncResult.databaseError = true; + } catch (InvalidAccountException e) { + App.log.log(Level.SEVERE, "Couldn't get account settings", e); } App.log.info("Calendar sync complete"); diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java index 5ca82c3c..4acda5b7 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java @@ -24,9 +24,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.List; +import java.util.logging.Level; import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.ServiceDB.Collections; @@ -55,10 +57,14 @@ public class ContactsSyncAdapterService extends SyncAdapterService { Long service = getService(account); if (service != null) { CollectionInfo remote = remoteAddressBook(service); - if (remote != null) { - ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, authority, provider, syncResult, remote); - syncManager.performSync(); - } else + if (remote != null) + try { + ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, authority, provider, syncResult, remote); + syncManager.performSync(); + } catch (InvalidAccountException e) { + App.log.log(Level.SEVERE, "Couldn't get account settings", e); + } + else App.log.info("No address book collection selected for synchronization"); } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java index 0d510d45..7639fa51 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java @@ -39,6 +39,7 @@ import at.bitfire.davdroid.App; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.resource.LocalAddressBook; @@ -68,7 +69,7 @@ public class ContactsSyncManager extends SyncManager { private boolean hasVCard4; - public ContactsSyncManager(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, CollectionInfo remote) { + public ContactsSyncManager(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, CollectionInfo remote) throws InvalidAccountException { super(Constants.NOTIFICATION_CONTACTS_SYNC, context, account, extras, authority, result); this.provider = provider; this.remote = remote; @@ -291,7 +292,7 @@ public class ContactsSyncManager extends SyncManager { return null; } - OkHttpClient resourceClient = HttpClient.create(context, null); + OkHttpClient resourceClient = HttpClient.create(); // authenticate only against a certain host, and only upon request resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password()); diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.java index d85b865c..370401ec 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.java @@ -20,8 +20,11 @@ import android.os.Bundle; import android.os.IBinder; import android.support.annotation.Nullable; +import java.util.logging.Level; + import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.model.ServiceDB; public abstract class SyncAdapterService extends Service { @@ -62,7 +65,11 @@ public abstract class SyncAdapterService extends Service { Thread.currentThread().setContextClassLoader(getContext().getClassLoader()); // peek into AccountSettings to cause possible migration (v0.9 -> v1.0) - new AccountSettings(getContext(), account); + try { + new AccountSettings(getContext(), account); + } catch (InvalidAccountException e) { + App.log.log(Level.SEVERE, "Couldn't check for updated account settings", e); + } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java index 74a6f64b..a337af36 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -42,6 +42,7 @@ import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.App; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalResource; @@ -99,7 +100,7 @@ abstract public class SyncManager { - public SyncManager(int notificationId, Context context, Account account, Bundle extras, String authority, SyncResult syncResult) { + public SyncManager(int notificationId, Context context, Account account, Bundle extras, String authority, SyncResult syncResult) throws InvalidAccountException { this.context = context; this.account = account; this.extras = extras; diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java index 22ee677a..a9b93ec0 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.logging.Level; import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB.Collections; import at.bitfire.davdroid.model.ServiceDB.OpenHelper; @@ -68,6 +69,8 @@ public class TasksSyncAdapterService extends SyncAdapterService { } } catch (CalendarStorageException e) { App.log.log(Level.SEVERE, "Couldn't enumerate local task lists", e); + } catch (InvalidAccountException e) { + App.log.log(Level.SEVERE, "Couldn't get account settings", e); } finally { db.close(); } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java index 4d24e6df..3488d699 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java @@ -43,6 +43,7 @@ import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.App; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalResource; import at.bitfire.davdroid.resource.LocalTask; @@ -64,7 +65,7 @@ public class TasksSyncManager extends SyncManager { final protected TaskProvider provider; - public TasksSyncManager(Context context, Account account, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList) { + public TasksSyncManager(Context context, Account account, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList) throws InvalidAccountException { super(Constants.NOTIFICATION_TASK_SYNC, context, account, extras, authority, result); this.provider = provider; localCollection = taskList; diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java index 2ded4f85..53e4b1b3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java @@ -66,6 +66,7 @@ import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.App; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.DavService; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB; @@ -275,6 +276,12 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu public void onLoadFinished(Loader loader, final AccountInfo info) { accountInfo = info; + if (accountInfo == null) { + // account doesn't exist anymore + finish(); + return; + } + CardView card = (CardView)findViewById(R.id.carddav); if (info.carddav != null) { ProgressBar progress = (ProgressBar)findViewById(R.id.carddav_refreshing); @@ -368,9 +375,13 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu public AccountInfo loadInBackground() { // peek into AccountSettings to call possible 0.9 -> 1.0 migration // The next line can be removed as soon as migration from 0.9 is not required anymore! - new AccountSettings(getContext(), new Account(accountName, Constants.ACCOUNT_TYPE)); + try { + new AccountSettings(getContext(), new Account(accountName, Constants.ACCOUNT_TYPE)); + } catch (InvalidAccountException e) { + App.log.log(Level.INFO, "Account doesn't exist (anymore)", e); + return null; + } - // get account info AccountInfo info = new AccountInfo(); try { SQLiteDatabase db = dbHelper.getReadableDatabase(); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountSettingsActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/AccountSettingsActivity.java index 5a88c724..1e013c27 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountSettingsActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountSettingsActivity.java @@ -24,7 +24,11 @@ import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.SwitchPreferenceCompat; import android.view.MenuItem; +import java.util.logging.Level; + import at.bitfire.davdroid.AccountSettings; +import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.ical4android.TaskProvider; @@ -77,7 +81,15 @@ public class AccountSettingsActivity extends AppCompatActivity { } public void refresh() { - final AccountSettings settings = new AccountSettings(getActivity(), account); + final AccountSettings settings; + + try { + settings = new AccountSettings(getActivity(), account); + } catch(InvalidAccountException e) { + App.log.log(Level.INFO, "Account is invalid or doesn't exist (anymore)", e); + getActivity().finish(); + return; + } // category: authentication final EditTextPreference prefUserName = (EditTextPreference)findPreference("username"); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java index d962a747..852ceaf2 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java @@ -36,6 +36,7 @@ import at.bitfire.dav4android.exception.HttpException; import at.bitfire.davdroid.App; import at.bitfire.davdroid.DavUtils; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB; @@ -208,9 +209,10 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); - OkHttpClient client = HttpClient.create(getContext(), account); - DavResource collection = new DavResource(client, HttpUrl.parse(info.url)); try { + OkHttpClient client = HttpClient.create(getContext(), account); + DavResource collection = new DavResource(client, HttpUrl.parse(info.url)); + // create collection on remote server collection.mkCol(writer.toString()); @@ -237,7 +239,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa ContentValues values = info.toDB(); values.put(ServiceDB.Collections.SERVICE_ID, serviceID); db.insert(ServiceDB.Collections._TABLE, null, values); - } catch(IOException|HttpException|IllegalStateException e) { + } catch(InvalidAccountException|IOException|HttpException|IllegalStateException e) { return e; } finally { dbHelper.close(); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java index 40ad4ea0..2f71bb4b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java @@ -48,6 +48,7 @@ import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.App; import at.bitfire.davdroid.BuildConfig; import at.bitfire.davdroid.Constants; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.ServiceDB; import lombok.Cleanup; @@ -185,7 +186,6 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage if (exception != null) report .append("\nEXCEPTION:\n") - .append(ExceptionUtils.getMessage(exception)).append("\n") .append(ExceptionUtils.getStackTrace(exception)); if (logs != null) @@ -212,14 +212,17 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage "CONFIGURATION\n" + "System-wide synchronization: ").append(ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually").append("\n"); AccountManager accountManager = AccountManager.get(getContext()); - for (Account acct : accountManager.getAccountsByType(Constants.ACCOUNT_TYPE)) { - AccountSettings settings = new AccountSettings(getContext(), acct); - report.append( - "Account: ").append(acct.name).append("\n" + - " Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" + - " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + - " OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n"); - } + for (Account acct : accountManager.getAccountsByType(Constants.ACCOUNT_TYPE)) + try { + AccountSettings settings = new AccountSettings(getContext(), acct); + report.append( + "Account: ").append(acct.name).append("\n" + + " Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" + + " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + + " OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n"); + } catch(InvalidAccountException e) { + report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n"); + } report.append("\n"); report.append("SQLITE DUMP\n"); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java index 4c9ec7e5..2c63a1f5 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java @@ -29,6 +29,7 @@ import java.io.IOException; import at.bitfire.dav4android.DavResource; import at.bitfire.dav4android.exception.HttpException; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB; @@ -109,10 +110,10 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa @Override public Exception loadInBackground() { - OkHttpClient httpClient = HttpClient.create(getContext(), account); - - DavResource collection = new DavResource(httpClient, HttpUrl.parse(collectionInfo.url)); try { + OkHttpClient httpClient = HttpClient.create(getContext(), account); + DavResource collection = new DavResource(httpClient, HttpUrl.parse(collectionInfo.url)); + // delete collection from server collection.delete(null); @@ -121,7 +122,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa db.delete(ServiceDB.Collections._TABLE, ServiceDB.Collections.ID + "=?", new String[] { String.valueOf(collectionInfo.id) }); return null; - } catch (IOException|HttpException e) { + } catch (InvalidAccountException|IOException|HttpException e) { return e; } finally { dbHelper.close(); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java index 353d3488..c1d834e1 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java @@ -92,15 +92,16 @@ public class AccountDetailsFragment extends Fragment { protected boolean createAccount(String accountName, DavResourceFinder.Configuration config) { Account account = new Account(accountName, Constants.ACCOUNT_TYPE); - App.log.log(Level.INFO, "Creating account " + accountName + " with initial config", config); - // create Android account - AccountManager accountManager = AccountManager.get(getContext()); Bundle userData = AccountSettings.initialUserData(config.userName, config.preemptive); + App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, userData }); + + AccountManager accountManager = AccountManager.get(getContext()); if (!accountManager.addAccountExplicitly(account, config.password, userData)) return false; // add entries for account to service DB + App.log.log(Level.INFO, "Writing account configuration to database", config); @Cleanup OpenHelper dbHelper = new OpenHelper(getContext()); SQLiteDatabase db = dbHelper.getWritableDatabase(); db.beginTransactionNonExclusive(); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.java index 6d660451..5aee675b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.java @@ -45,6 +45,7 @@ import at.bitfire.dav4android.property.DisplayName; import at.bitfire.dav4android.property.ResourceType; import at.bitfire.dav4android.property.SupportedCalendarComponentSet; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.log.StringHandler; import at.bitfire.davdroid.model.CollectionInfo; import lombok.RequiredArgsConstructor; @@ -76,7 +77,7 @@ public class DavResourceFinder { log = Logger.getLogger("davdroid.DavResourceFinder"); log.addHandler(logBuffer); - httpClient = HttpClient.create(context, null, log); + httpClient = HttpClient.create(log); httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password, credentials.authPreemptive); } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.java index 76cb4860..f011104b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.java @@ -21,6 +21,8 @@ import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.support.v7.app.AlertDialog; +import at.bitfire.davdroid.App; +import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.ui.DebugInfoActivity; import at.bitfire.davdroid.ui.setup.DavResourceFinder.Configuration; @@ -62,17 +64,20 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade @Override public void onLoadFinished(Loader loader, Configuration 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(android.R.id.content, AccountDetailsFragment.newInstance(data)) - .addToBackStack(null) - .commitAllowingStateLoss(); + if (data != null) { + 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(android.R.id.content, AccountDetailsFragment.newInstance(data)) + .addToBackStack(null) + .commitAllowingStateLoss(); + } else + App.log.severe("Configuration detection failed"); dismissAllowingStateLoss(); } @@ -94,6 +99,7 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade } @Override + @NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setTitle(R.string.login_configuration_detection) @@ -134,8 +140,7 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade @Override public Configuration loadInBackground() { - DavResourceFinder finder = new DavResourceFinder(context, credentials); - return finder.findInitialConfiguration(); + return new DavResourceFinder(context, credentials).findInitialConfiguration(); } } }