diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e7c9dc60..d0d5b632 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,11 +55,11 @@
tools:ignore="UnusedAttribute">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
sqlAccountNames = new LinkedList<>();
+ List accountNames = new LinkedList<>();
AccountManager am = AccountManager.get(this);
- for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE))
- sqlAccountNames.add(account.name);
+ for (Account account : am.getAccountsByType(App.getAccountType())) {
+ accountNames.add(account.name);
+ }
EntityDataStore data = ((App) getApplication()).getData();
- if (sqlAccountNames.isEmpty()) {
+ // delete orphaned address book accounts
+ for (Account addrBookAccount : am.getAccountsByType(App.getAddressBookAccountType())) {
+ LocalAddressBook addressBook = new LocalAddressBook(this, addrBookAccount, null);
+ try {
+ if (!accountNames.contains(addressBook.getMainAccount().name))
+ addressBook.delete();
+ } catch(ContactsStorageException e) {
+ App.log.log(Level.SEVERE, "Couldn't get address book main account", e);
+ }
+ }
+
+
+ if (accountNames.isEmpty()) {
data.delete(ServiceEntity.class).get().value();
} else {
- data.delete(ServiceEntity.class).where(ServiceEntity.ACCOUNT.notIn(sqlAccountNames)).get().value();
+ data.delete(ServiceEntity.class).where(ServiceEntity.ACCOUNT.notIn(accountNames)).get().value();
}
}
}
diff --git a/app/src/main/java/com/etesync/syncadapter/App.java b/app/src/main/java/com/etesync/syncadapter/App.java
index 7b95c3c3..11d599ab 100644
--- a/app/src/main/java/com/etesync/syncadapter/App.java
+++ b/app/src/main/java/com/etesync/syncadapter/App.java
@@ -78,8 +78,6 @@ import okhttp3.internal.tls.OkHostnameVerifier;
public class App extends Application {
- public static final String FLAVOR_GOOGLE_PLAY = "gplay";
-
public static final String
DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts",
LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage",
@@ -90,6 +88,9 @@ public class App extends Application {
public static final String OVERRIDE_PROXY_HOST_DEFAULT = "localhost";
public static final int OVERRIDE_PROXY_PORT_DEFAULT = 8118;
+ @Getter
+ private static String appName;
+
@Getter
private CustomCertManager certManager;
@@ -107,6 +108,13 @@ public class App extends Application {
at.bitfire.cert4android.Constants.log = Logger.getLogger("syncadapter.cert4android");
}
+ @Getter
+ private static String accountType;
+ @Getter
+ private static String addressBookAccountType;
+ @Getter
+ private static String addressBooksAuthority;
+
@Override
@SuppressLint("HardwareIds")
public void onCreate() {
@@ -117,6 +125,11 @@ public class App extends Application {
initPrefVersion();
uidGenerator = new UidGenerator(null, android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID));
+
+ appName = getString(R.string.app_name);
+ accountType = getString(R.string.account_type);
+ addressBookAccountType = getString(R.string.account_type_address_book);
+ addressBooksAuthority = getString(R.string.address_books_authority);
}
public void reinitCertManager() {
@@ -205,11 +218,13 @@ public class App extends Application {
}
- public static class ReinitLoggingReceiver extends BroadcastReceiver {
+ public static class ReinitSettingsReceiver extends BroadcastReceiver {
+
+ public static final String ACTION_REINIT_SETTINGS = BuildConfig.APPLICATION_ID + ".REINIT_SETTINGS";
@Override
public void onReceive(Context context, Intent intent) {
- log.info("Received broadcast: re-initializing logger");
+ log.info("Received broadcast: re-initializing settings (logger/cert manager)");
App app = (App)context.getApplicationContext();
app.reinitLogger();
@@ -299,18 +314,23 @@ public class App extends Application {
if (fromVersion < 7) {
/* Fix all of the etags to be non-null */
AccountManager am = AccountManager.get(this);
- for (Account account : am.getAccountsByType(Constants.ACCOUNT_TYPE)) {
+ for (Account account : am.getAccountsByType(App.getAccountType())) {
try {
+ // Generate account settings to make sure account is migrated.
+ new AccountSettings(this, account);
+
LocalCalendar calendars[] = (LocalCalendar[]) LocalCalendar.find(account, this.getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI),
LocalCalendar.Factory.INSTANCE, null, null);
for (LocalCalendar calendar : calendars) {
calendar.fixEtags();
}
- } catch (CalendarStorageException e) {
+ } catch (CalendarStorageException|InvalidAccountException e) {
e.printStackTrace();
}
+ }
- LocalAddressBook addressBook = new LocalAddressBook(account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI));
+ for (Account account : am.getAccountsByType(App.getAddressBookAccountType())) {
+ LocalAddressBook addressBook = new LocalAddressBook(this, account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI));
try {
addressBook.fixEtags();
} catch (ContactsStorageException e) {
diff --git a/app/src/main/java/com/etesync/syncadapter/Constants.java b/app/src/main/java/com/etesync/syncadapter/Constants.java
index 8b203b3b..fa7b1875 100644
--- a/app/src/main/java/com/etesync/syncadapter/Constants.java
+++ b/app/src/main/java/com/etesync/syncadapter/Constants.java
@@ -13,9 +13,6 @@ import static com.etesync.syncadapter.BuildConfig.DEBUG_REMOTE_URL;
public class Constants {
- public static final String
- ACCOUNT_TYPE = "com.etesync.syncadapter";
-
// notification IDs
public final static int
NOTIFICATION_ACCOUNT_SETTINGS_UPDATED = 0,
diff --git a/app/src/main/java/com/etesync/syncadapter/HttpClient.java b/app/src/main/java/com/etesync/syncadapter/HttpClient.java
index 5fd080a8..3d7c6533 100644
--- a/app/src/main/java/com/etesync/syncadapter/HttpClient.java
+++ b/app/src/main/java/com/etesync/syncadapter/HttpClient.java
@@ -8,13 +8,15 @@
package com.etesync.syncadapter;
-import android.accounts.Account;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import com.etesync.syncadapter.model.ServiceDB;
+import com.etesync.syncadapter.model.Settings;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
@@ -25,8 +27,6 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
-import com.etesync.syncadapter.model.ServiceDB;
-import com.etesync.syncadapter.model.Settings;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -41,7 +41,7 @@ public class HttpClient {
static {
String date = new SimpleDateFormat("yyyy/MM/dd", Locale.US).format(new Date(BuildConfig.buildTime));
- userAgent = "EteSync/" + BuildConfig.VERSION_NAME + " (" + date + "; okhttp3) Android/" + Build.VERSION.RELEASE;
+ userAgent = App.getAppName() + "/" + BuildConfig.VERSION_NAME + " (" + date + "; okhttp3) Android/" + Build.VERSION.RELEASE;
}
private HttpClient() {
@@ -56,9 +56,7 @@ public class HttpClient {
return builder.build();
}
- public static OkHttpClient create(@Nullable Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException {
- // use account settings for authentication
- AccountSettings settings = new AccountSettings(context, account);
+ public static OkHttpClient create(@Nullable Context context, @NonNull AccountSettings settings, @NonNull final Logger logger) {
return create(context, logger, Constants.serviceUrl.getHost(), settings.getAuthToken());
}
@@ -66,8 +64,8 @@ public class HttpClient {
return defaultBuilder(context, logger).build();
}
- public static OkHttpClient create(@NonNull Context context, @NonNull Account account) throws InvalidAccountException {
- return create(context, account, App.log);
+ public static OkHttpClient create(@NonNull Context context, @NonNull AccountSettings settings) {
+ return create(context, settings, App.log);
}
public static OkHttpClient create(@Nullable Context context) {
diff --git a/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java b/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java
index 7e6b4fe3..06c1adf4 100644
--- a/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java
+++ b/app/src/main/java/com/etesync/syncadapter/model/JournalModel.java
@@ -68,19 +68,11 @@ public class JournalModel {
}
public static List getJournals(EntityDataStore data, ServiceEntity serviceEntity) {
- return data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.DELETED.eq(false))).get().toList();
- }
-
- public static List getCollections(EntityDataStore data, ServiceEntity serviceEntity) {
- List ret = new LinkedList<>();
-
- List journals = getJournals(data, serviceEntity);
- for (JournalEntity journal : journals) {
+ List ret = data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.DELETED.eq(false))).get().toList();
+ for (JournalEntity journal : ret) {
// FIXME: For some reason this isn't always being called, manually do it here.
journal.afterLoad();
- ret.add(journal.getInfo());
}
-
return ret;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java
index d6b8e8ca..44691e69 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.java
@@ -8,25 +8,35 @@
package com.etesync.syncadapter.resource;
import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Parcel;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.os.OperationCanceledException;
import com.etesync.syncadapter.App;
+import com.etesync.syncadapter.model.CollectionInfo;
+import com.etesync.syncadapter.model.JournalEntity;
+import com.etesync.syncadapter.utils.AndroidCompat;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
@@ -45,9 +55,11 @@ import lombok.Cleanup;
public class LocalAddressBook extends AndroidAddressBook implements LocalCollection {
protected static final String
- SYNC_STATE_CTAG = "ctag",
- SYNC_STATE_URL = "url";
+ USER_DATA_MAIN_ACCOUNT_TYPE = "real_account_type",
+ USER_DATA_MAIN_ACCOUNT_NAME = "real_account_name",
+ USER_DATA_URL = "url";
+ protected final Context context;
private final Bundle syncState = new Bundle();
/**
@@ -58,8 +70,85 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
public boolean includeGroups = true;
- public LocalAddressBook(Account account, ContentProviderClient provider) {
+ public static LocalAddressBook[] find(@NonNull Context context, @NonNull ContentProviderClient provider, @Nullable Account mainAccount) throws ContactsStorageException {
+ AccountManager accountManager = AccountManager.get(context);
+
+ List result = new LinkedList<>();
+ for (Account account : accountManager.getAccountsByType(App.getAddressBookAccountType())) {
+ LocalAddressBook addressBook = new LocalAddressBook(context, account, provider);
+ if (mainAccount == null || addressBook.getMainAccount().equals(mainAccount))
+ result.add(addressBook);
+ }
+
+ return result.toArray(new LocalAddressBook[result.size()]);
+ }
+
+ public static LocalAddressBook findByUid(@NonNull Context context, @NonNull ContentProviderClient provider, @Nullable Account mainAccount, String uid) throws ContactsStorageException {
+ AccountManager accountManager = AccountManager.get(context);
+
+ for (Account account : accountManager.getAccountsByType(App.getAddressBookAccountType())) {
+ LocalAddressBook addressBook = new LocalAddressBook(context, account, provider);
+ if (addressBook.getURL().equals(uid) && (mainAccount == null || addressBook.getMainAccount().equals(mainAccount)))
+ return addressBook;
+ }
+
+ return null;
+ }
+
+ public static LocalAddressBook create(@NonNull Context context, @NonNull ContentProviderClient provider, @NonNull Account mainAccount, @NonNull JournalEntity journalEntity) throws ContactsStorageException {
+ CollectionInfo info = journalEntity.getInfo();
+ AccountManager accountManager = AccountManager.get(context);
+
+ Account account = new Account(accountName(mainAccount, info), App.getAddressBookAccountType());
+ if (!accountManager.addAccountExplicitly(account, null, null))
+ throw new ContactsStorageException("Couldn't create address book account");
+
+ setUserData(accountManager, account, mainAccount, info.uid);
+ LocalAddressBook addressBook = new LocalAddressBook(context, account, provider);
+ addressBook.setMainAccount(mainAccount);
+ addressBook.setURL(info.uid);
+
+ ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
+
+ return addressBook;
+ }
+
+ public void update(@NonNull JournalEntity journalEntity) throws AuthenticatorException, OperationCanceledException, IOException, ContactsStorageException, android.accounts.OperationCanceledException {
+ CollectionInfo info = journalEntity.getInfo();
+ final String newAccountName = accountName(getMainAccount(), info);
+ if (!account.name.equals(newAccountName) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ final AccountManager accountManager = AccountManager.get(context);
+ AccountManagerFuture future = accountManager.renameAccount(account, newAccountName, new AccountManagerCallback() {
+ @Override
+ public void run(AccountManagerFuture future) {
+ try {
+ // update raw contacts to new account name
+ if (provider != null) {
+ ContentValues values = new ContentValues(1);
+ values.put(RawContacts.ACCOUNT_NAME, newAccountName);
+ provider.update(syncAdapterURI(RawContacts.CONTENT_URI), values, RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?",
+ new String[] { account.name, account.type });
+ }
+ } catch(RemoteException e) {
+ App.log.log(Level.WARNING, "Couldn't re-assign contacts to new account name", e);
+ }
+ }
+ }, null);
+ account = future.getResult();
+ }
+
+ // make sure it will still be synchronized when contacts are updated
+ ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
+ }
+
+ public void delete() {
+ AccountManager accountManager = AccountManager.get(context);
+ AndroidCompat.removeAccount(accountManager, account);
+ }
+
+ public LocalAddressBook(Context context, Account account, ContentProviderClient provider) {
super(account, provider, LocalGroup.Factory.INSTANCE, LocalContact.Factory.INSTANCE);
+ this.context = context;
}
@NonNull
@@ -268,51 +357,52 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
}
- // SYNC STATE
+ // SETTINGS
- @SuppressWarnings("ParcelClassLoader,Recycle")
- protected void readSyncState() throws ContactsStorageException {
- @Cleanup("recycle") Parcel parcel = Parcel.obtain();
- byte[] raw = getSyncState();
- syncState.clear();
- if (raw != null) {
- parcel.unmarshall(raw, 0, raw.length);
- parcel.setDataPosition(0);
- syncState.putAll(parcel.readBundle());
- }
+ // XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work.
+ public static void setUserData(@NonNull AccountManager accountManager, @NonNull Account account, @NonNull Account mainAccount, @NonNull String url) {
+ accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name);
+ accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE, mainAccount.type);
+ accountManager.setUserData(account, USER_DATA_URL, url);
}
- @SuppressWarnings("Recycle")
- protected void writeSyncState() throws ContactsStorageException {
- @Cleanup("recycle") Parcel parcel = Parcel.obtain();
- parcel.writeBundle(syncState);
- setSyncState(parcel.marshall());
+ public Account getMainAccount() throws ContactsStorageException {
+ AccountManager accountManager = AccountManager.get(context);
+ String name = accountManager.getUserData(account, USER_DATA_MAIN_ACCOUNT_NAME),
+ type = accountManager.getUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE);
+ if (name != null && type != null)
+ return new Account(name, type);
+ else
+ throw new ContactsStorageException("Address book doesn't exist anymore");
+ }
+
+ public void setMainAccount(@NonNull Account mainAccount) throws ContactsStorageException {
+ AccountManager accountManager = AccountManager.get(context);
+ accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_NAME, mainAccount.name);
+ accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE, mainAccount.type);
}
public String getURL() throws ContactsStorageException {
- synchronized (syncState) {
- readSyncState();
- return syncState.getString(SYNC_STATE_URL);
- }
+ AccountManager accountManager = AccountManager.get(context);
+ return accountManager.getUserData(account, USER_DATA_URL);
}
public void setURL(String url) throws ContactsStorageException {
- synchronized (syncState) {
- readSyncState();
- syncState.putString(SYNC_STATE_URL, url);
- writeSyncState();
- }
+ AccountManager accountManager = AccountManager.get(context);
+ accountManager.setUserData(account, USER_DATA_URL, url);
}
// HELPERS
- public static void onRenameAccount(@NonNull ContentResolver resolver, @NonNull String oldName, @NonNull String newName) throws RemoteException {
- @Cleanup("release") ContentProviderClient client = resolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
- if (client != null) {
- ContentValues values = new ContentValues(1);
- values.put(RawContacts.ACCOUNT_NAME, newName);
- client.update(RawContacts.CONTENT_URI, values, RawContacts.ACCOUNT_NAME + "=?", new String[]{oldName});
- }
+ public static String accountName(@NonNull Account mainAccount, @NonNull CollectionInfo info) {
+ String displayName = (info.displayName != null) ? info.displayName : info.uid;
+ StringBuilder sb = new StringBuilder(displayName);
+ sb .append(" (")
+ .append(mainAccount.name)
+ .append(" ")
+ .append(info.uid.substring(0, 4))
+ .append(")");
+ return sb.toString();
}
/** Fix all of the etags of all of the non-dirty contacts to be non-null.
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java
new file mode 100644
index 00000000..14bdf90d
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBookProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 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 com.etesync.syncadapter.syncadapter;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class AddressBookProvider extends ContentProvider {
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java
new file mode 100644
index 00000000..ef5ed8ea
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright © 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
+ */
+package com.etesync.syncadapter.syncadapter;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.database.sqlite.SQLiteException;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+
+import com.etesync.syncadapter.AccountSettings;
+import com.etesync.syncadapter.App;
+import com.etesync.syncadapter.Constants;
+import com.etesync.syncadapter.NotificationHelper;
+import com.etesync.syncadapter.R;
+import com.etesync.syncadapter.journalmanager.Exceptions;
+import com.etesync.syncadapter.model.CollectionInfo;
+import com.etesync.syncadapter.model.JournalEntity;
+import com.etesync.syncadapter.model.JournalModel;
+import com.etesync.syncadapter.model.ServiceEntity;
+import com.etesync.syncadapter.resource.LocalAddressBook;
+import com.etesync.syncadapter.ui.DebugInfoActivity;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+
+import at.bitfire.vcard4android.ContactsStorageException;
+import io.requery.Persistable;
+import io.requery.sql.EntityDataStore;
+import lombok.Cleanup;
+
+import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
+
+public class AddressBooksSyncAdapterService extends SyncAdapterService {
+
+ @Override
+ protected AbstractThreadedSyncAdapter syncAdapter() {
+ return new AddressBooksSyncAdapter(this);
+ }
+
+
+ private static class AddressBooksSyncAdapter extends SyncAdapter {
+
+ public AddressBooksSyncAdapter(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
+ super.onPerformSync(account, extras, authority, provider, syncResult);
+
+ NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC);
+ notificationManager.cancel();
+
+ try {
+ @Cleanup("release") ContentProviderClient contactsProvider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
+ if (contactsProvider == null) {
+ App.log.severe("Couldn't access contacts provider");
+ syncResult.databaseError = true;
+ return;
+ }
+
+ AccountSettings settings = new AccountSettings(getContext(), account);
+ if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
+ return;
+
+ new RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run();
+
+ updateLocalAddressBooks(contactsProvider, account);
+
+ AccountManager accountManager = AccountManager.get(getContext());
+ for (Account addressBookAccount : accountManager.getAccountsByType(getContext().getString(R.string.account_type_address_book))) {
+ App.log.log(Level.INFO, "Running sync for address book", addressBookAccount);
+ Bundle syncExtras = new Bundle(extras);
+ syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ ContentResolver.requestSync(addressBookAccount, ContactsContract.AUTHORITY, syncExtras);
+ }
+ } catch (Exceptions.ServiceUnavailableException e) {
+ syncResult.stats.numIoExceptions++;
+ syncResult.delayUntil = (e.retryAfter > 0) ? e.retryAfter : Constants.DEFAULT_RETRY_DELAY;
+ } catch (Exception | OutOfMemoryError e) {
+ if (e instanceof ContactsStorageException || e instanceof SQLiteException) {
+ App.log.log(Level.SEVERE, "Couldn't prepare local address books", e);
+ syncResult.databaseError = true;
+ }
+
+ int syncPhase = R.string.sync_phase_journals;
+ String title = getContext().getString(R.string.sync_error_contacts, account.name);
+
+ notificationManager.setThrowable(e);
+
+ final Intent detailsIntent = notificationManager.getDetailsIntent();
+ detailsIntent.putExtra(KEY_ACCOUNT, account);
+ if (!(e instanceof Exceptions.UnauthorizedException)) {
+ detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
+ detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
+ }
+
+ notificationManager.notify(title, getContext().getString(syncPhase));
+ }
+
+ App.log.info("Address book sync complete");
+ }
+
+
+ private void updateLocalAddressBooks(ContentProviderClient provider, Account account) throws ContactsStorageException, AuthenticatorException, OperationCanceledException, IOException {
+ final Context context = getContext();
+ EntityDataStore data = ((App) getContext().getApplicationContext()).getData();
+ ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.ADDRESS_BOOK);
+
+ Map remote = new HashMap<>();
+ List remoteJournals = JournalEntity.getJournals(data, service);
+ for (JournalEntity journalEntity : remoteJournals) {
+ remote.put(journalEntity.getUid(), journalEntity);
+ }
+
+ LocalAddressBook[] local = LocalAddressBook.find(context, provider, account);
+
+ // delete obsolete local address books
+ for (LocalAddressBook addressBook : local) {
+ String url = addressBook.getURL();
+ if (!remote.containsKey(url)) {
+ App.log.fine("Deleting obsolete local address book " + url);
+ addressBook.delete();
+ } else {
+ // remote CollectionInfo found for this local collection, update data
+ JournalEntity journalEntity = remote.get(url);
+ App.log.fine("Updating local address book " + url + " with " + journalEntity);
+ addressBook.update(journalEntity);
+ // we already have a local collection for this remote collection, don't take into consideration anymore
+ remote.remove(url);
+ }
+ }
+
+ // create new local address books
+ for (String url : remote.keySet()) {
+ JournalEntity journalEntity = remote.get(url);
+ App.log.info("Adding local address book " + journalEntity);
+ LocalAddressBook.create(context, provider, account, journalEntity);
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java
index de871b88..00b4a807 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java
@@ -13,16 +13,9 @@ import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
-import org.apache.commons.codec.Charsets;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
-import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalEntryManager;
@@ -31,6 +24,13 @@ import com.etesync.syncadapter.model.SyncEntry;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.resource.LocalEvent;
import com.etesync.syncadapter.resource.LocalResource;
+
+import org.apache.commons.codec.Charsets;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.ical4android.Event;
import at.bitfire.ical4android.InvalidCalendarException;
@@ -45,8 +45,8 @@ public class CalendarSyncManager extends SyncManager {
final private HttpUrl remote;
- public CalendarSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult result, LocalCalendar calendar, HttpUrl remote) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException {
- super(context, account, settings, extras, authority, result, calendar.getName(), CollectionInfo.Type.CALENDAR);
+ public CalendarSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult result, LocalCalendar calendar, HttpUrl remote) throws Exceptions.IntegrityException, Exceptions.GenericCryptoException {
+ super(context, account, settings, extras, authority, result, calendar.getName(), CollectionInfo.Type.CALENDAR, account.name);
localCollection = calendar;
this.remote = remote;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java
index 9c63c8a2..2f2d6de7 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.java
@@ -28,10 +28,12 @@ import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.JournalModel;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
+import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.ui.DebugInfoActivity;
import java.util.logging.Level;
+import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import okhttp3.HttpUrl;
@@ -59,30 +61,26 @@ public class ContactsSyncAdapterService extends SyncAdapterService {
notificationManager.cancel();
try {
- AccountSettings settings = new AccountSettings(getContext(), account);
+ LocalAddressBook addressBook = new LocalAddressBook(getContext(), account, provider);
+
+ AccountSettings settings;
+ try {
+ settings = new AccountSettings(getContext(), addressBook.getMainAccount());
+ } catch (InvalidAccountException|ContactsStorageException e) {
+ App.log.info("Skipping sync due to invalid account.");
+ App.log.info(e.getLocalizedMessage());
+ return;
+ }
+
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return;
- new RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run();
+ App.log.info("Synchronizing address book: " + addressBook.getURL());
+ App.log.info("Taking settings from: " + addressBook.getMainAccount());
- EntityDataStore data = ((App) getContext().getApplicationContext()).getData();
-
- ServiceEntity service = JournalModel.Service.fetch(data, account.name, CollectionInfo.Type.ADDRESS_BOOK);
-
- if (service != null) {
- HttpUrl principal = HttpUrl.get(settings.getUri());
- CollectionInfo info = JournalEntity.getCollections(data, service).get(0);
- try {
- ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, principal, info);
- syncManager.performSync();
- } catch (InvalidAccountException e) {
- App.log.log(Level.SEVERE, "Couldn't get account settings", e);
- }
- } else
- App.log.info("No CardDAV service found in DB");
- } catch (Exceptions.ServiceUnavailableException e) {
- syncResult.stats.numIoExceptions++;
- syncResult.delayUntil = (e.retryAfter > 0) ? e.retryAfter : Constants.DEFAULT_RETRY_DELAY;
+ HttpUrl principal = HttpUrl.get(settings.getUri());
+ ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, addressBook, principal);
+ syncManager.performSync();
} catch (Exception | OutOfMemoryError e) {
int syncPhase = R.string.sync_phase_journals;
String title = getContext().getString(R.string.sync_error_contacts, account.name);
@@ -98,7 +96,7 @@ public class ContactsSyncAdapterService extends SyncAdapterService {
notificationManager.notify(title, getContext().getString(syncPhase));
}
- App.log.info("Address book sync complete");
+ App.log.info("Contacts sync complete");
}
}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java
index 2b6be832..a8e2917f 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.java
@@ -18,19 +18,10 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
-import org.apache.commons.codec.Charsets;
-import org.apache.commons.io.IOUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.logging.Level;
-
import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.HttpClient;
-import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalEntryManager;
@@ -40,6 +31,15 @@ import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalContact;
import com.etesync.syncadapter.resource.LocalGroup;
import com.etesync.syncadapter.resource.LocalResource;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.io.IOUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.vcard4android.Contact;
import at.bitfire.vcard4android.ContactsStorageException;
@@ -60,10 +60,12 @@ public class ContactsSyncManager extends SyncManager {
final private ContentProviderClient provider;
final private HttpUrl remote;
- public ContactsSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, HttpUrl principal, CollectionInfo info) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException {
- super(context, account, settings, extras, authority, result, info.uid, CollectionInfo.Type.ADDRESS_BOOK);
+ public ContactsSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, LocalAddressBook localAddressBook, HttpUrl principal) throws Exceptions.IntegrityException, Exceptions.GenericCryptoException, ContactsStorageException {
+ super(context, account, settings, extras, authority, result, localAddressBook.getURL(), CollectionInfo.Type.ADDRESS_BOOK, localAddressBook.getMainAccount().name);
this.provider = provider;
this.remote = principal;
+
+ localCollection = localAddressBook;
}
@Override
@@ -80,10 +82,7 @@ public class ContactsSyncManager extends SyncManager {
protected boolean prepare() throws ContactsStorageException, CalendarStorageException {
if (!super.prepare())
return false;
- // prepare local address book
- localCollection = new LocalAddressBook(account, provider);
LocalAddressBook localAddressBook = localAddressBook();
- localAddressBook.setURL(info.uid);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
@@ -101,7 +100,7 @@ public class ContactsSyncManager extends SyncManager {
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
localAddressBook.updateSettings(values);
- journal = new JournalEntryManager(httpClient, remote, info.uid);
+ journal = new JournalEntryManager(httpClient, remote, localAddressBook.getURL());
return true;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java
new file mode 100644
index 00000000..4bbda624
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/NullAuthenticatorService.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright © 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 com.etesync.syncadapter.syncadapter;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import com.etesync.syncadapter.ui.AccountsActivity;
+
+public class NullAuthenticatorService extends Service {
+
+ private AccountAuthenticator accountAuthenticator;
+
+ @Override
+ public void onCreate() {
+ accountAuthenticator = new NullAuthenticatorService.AccountAuthenticator(this);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
+ return accountAuthenticator.getIBinder();
+ return null;
+ }
+
+
+ private static class AccountAuthenticator extends AbstractAccountAuthenticator {
+ final Context context;
+
+ public AccountAuthenticator(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ return null;
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
+ Intent intent = new Intent(context, AccountsActivity.class);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ return bundle;
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return null;
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) {
+ Bundle result = new Bundle();
+ boolean allowed = false; // we don't want users to explicitly delete inner accounts
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, allowed);
+ return result;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java
index 06946fc0..9b5a92f3 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.java
@@ -76,7 +76,7 @@ public abstract class SyncAdapterService extends Service {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
- App.log.log(Level.INFO, "Sync for " + authority + " has been initiated.", extras.keySet().toArray());
+ App.log.log(Level.INFO, authority + " sync of " + account + " has been initiated.", extras.keySet().toArray());
// required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
@@ -143,9 +143,9 @@ public abstract class SyncAdapterService extends Service {
void run() throws Exceptions.HttpException, Exceptions.IntegrityException, InvalidAccountException, Exceptions.GenericCryptoException {
App.log.info("Refreshing " + serviceType + " collections of service #" + serviceType.toString());
- OkHttpClient httpClient = HttpClient.create(context, account);
-
AccountSettings settings = new AccountSettings(context, account);
+ OkHttpClient httpClient = HttpClient.create(context, settings);
+
JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri()));
List> journals = new LinkedList<>();
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java
index 45531db8..441730f5 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java
@@ -18,7 +18,6 @@ import com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.HttpClient;
-import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.NotificationHelper;
import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Crypto;
@@ -97,7 +96,7 @@ abstract public class SyncManager {
private List localDeleted;
private LocalResource[] localDirty;
- public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String journalUid, CollectionInfo.Type serviceType) throws InvalidAccountException, Exceptions.IntegrityException, Exceptions.GenericCryptoException {
+ public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String journalUid, CollectionInfo.Type serviceType, String accountName) throws Exceptions.IntegrityException, Exceptions.GenericCryptoException {
this.context = context;
this.account = account;
this.settings = settings;
@@ -107,10 +106,10 @@ abstract public class SyncManager {
this.serviceType = serviceType;
// create HttpClient with given logger
- httpClient = HttpClient.create(context, account);
+ httpClient = HttpClient.create(context, settings);
data = ((App) context.getApplicationContext()).getData();
- ServiceEntity serviceEntity = JournalModel.Service.fetch(data, account.name, serviceType);
+ ServiceEntity serviceEntity = JournalModel.Service.fetch(data, accountName, serviceType);
info = JournalEntity.fetch(data, serviceEntity, journalUid).getInfo();
// dismiss previous error notifications
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.java
index 6cd33770..f749547b 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.java
@@ -72,7 +72,7 @@ public class AboutActivity extends AppCompatActivity {
private final static ComponentInfo components[] = {
new ComponentInfo(
- "EteSync", BuildConfig.VERSION_NAME, Constants.webUri.toString(),
+ App.getAppName(), BuildConfig.VERSION_NAME, Constants.webUri.toString(),
DateFormatUtils.format(BuildConfig.buildTime, "yyyy") + " Tom Hacohen",
R.string.about_license_info_no_warranty, "gpl-3.0-standalone.html"
), new ComponentInfo(
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java
index ad091144..723970c6 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java
@@ -58,6 +58,7 @@ import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceEntity;
+import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment;
import com.etesync.syncadapter.utils.HintManager;
@@ -69,6 +70,7 @@ import java.util.logging.Level;
import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.ical4android.TaskProvider;
+import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import tourguide.tourguide.ToolTip;
@@ -98,6 +100,9 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
// CardDAV toolbar
tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu);
+ tbCardDAV.setOverflowIcon(icMenu);
+ tbCardDAV.inflateMenu(R.menu.carddav_actions);
+ tbCardDAV.setOnMenuItemClickListener(this);
tbCardDAV.setTitle(R.string.settings_carddav);
// CalDAV toolbar
@@ -193,12 +198,18 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
@Override
public boolean onMenuItemClick(MenuItem item) {
+ CollectionInfo info;
switch (item.getItemId()) {
case R.id.create_calendar:
- CollectionInfo info = new CollectionInfo();
+ info = new CollectionInfo();
info.type = CollectionInfo.Type.CALENDAR;
startActivity(CreateCollectionActivity.newIntent(AccountActivity.this, account, info));
break;
+ case R.id.create_addressbook:
+ info = new CollectionInfo();
+ info.type = CollectionInfo.Type.ADDRESS_BOOK;
+ startActivity(CreateCollectionActivity.newIntent(AccountActivity.this, account, info));
+ break;
}
return false;
}
@@ -357,8 +368,18 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
if (service.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
info.carddav = new AccountInfo.ServiceInfo();
info.carddav.id = id;
- info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY);
+ info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, App.getAddressBooksAuthority());
info.carddav.journals = JournalEntity.getJournals(data, serviceEntity);
+
+ AccountManager accountManager = AccountManager.get(getContext());
+ for (Account addrBookAccount : accountManager.getAccountsByType(App.getAddressBookAccountType())) {
+ LocalAddressBook addressBook = new LocalAddressBook(getContext(), addrBookAccount, null);
+ try {
+ if (account.equals(addressBook.getMainAccount()))
+ info.carddav.refreshing |= ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY);
+ } catch(ContactsStorageException e) {
+ }
+ }
} else if (service.equals(CollectionInfo.Type.CALENDAR)) {
info.caldav = new AccountInfo.ServiceInfo();
info.caldav.id = id;
@@ -457,7 +478,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
protected static void requestSync(Account account) {
String authorities[] = {
- ContactsContract.AUTHORITY,
+ App.getAddressBooksAuthority(),
CalendarContract.AUTHORITY,
TaskProvider.ProviderName.OpenTasks.authority
};
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountListFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountListFragment.java
index 7a403687..a60e8d91 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AccountListFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountListFragment.java
@@ -29,6 +29,7 @@ import android.widget.ListView;
import android.widget.TextView;
import com.etesync.syncadapter.AccountsChangedReceiver;
+import com.etesync.syncadapter.App;
import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.R;
@@ -106,7 +107,7 @@ public class AccountListFragment extends ListFragment implements LoaderManager.L
@Override
@SuppressLint("MissingPermission")
public Account[] loadInBackground() {
- return accountManager.getAccountsByType(Constants.ACCOUNT_TYPE);
+ return accountManager.getAccountsByType(App.getAccountType());
}
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java
index d60377ad..ddc6e6f3 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.java
@@ -111,7 +111,7 @@ public class AccountSettingsActivity extends AppCompatActivity {
// category: synchronization
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
- final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
+ final Long syncIntervalContacts = settings.getSyncInterval(App.getAddressBooksAuthority());
if (syncIntervalContacts != null) {
prefSyncContacts.setValue(syncIntervalContacts.toString());
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
@@ -121,7 +121,7 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String)newValue));
+ settings.setSyncInterval(App.getAddressBooksAuthority(), Long.parseLong((String)newValue));
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
return false;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java
index dae2ba6c..d3bbe91d 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.java
@@ -55,7 +55,7 @@ public class AddMemberFragment extends DialogFragment {
memberEmail = getArguments().getString(KEY_MEMBER);
try {
settings = new AccountSettings(getContext(), account);
- httpClient = HttpClient.create(getContext(), account);
+ httpClient = HttpClient.create(getContext(), settings);
} catch (InvalidAccountException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.java
index 8e89dd4c..c388ed4d 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.java
@@ -161,6 +161,9 @@ public class AppSettingsActivity extends AppCompatActivity {
// re-initialize certificate manager
App app = (App)getContext().getApplicationContext();
app.reinitCertManager();
+
+ // reinitialize certificate manager of :sync process
+ getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
}
private void resetCertificates() {
@@ -176,7 +179,7 @@ public class AppSettingsActivity extends AppCompatActivity {
app.reinitLogger();
// reinitialize logger of :sync process
- getContext().sendBroadcast(new Intent("com.etesync.syncadapter.REINIT_LOGGER"));
+ getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
}
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java
index 73559d7b..b49aad06 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.java
@@ -130,8 +130,8 @@ public class CollectionMembersListFragment extends ListFragment implements Adapt
@Override
protected MembersResult doInBackground(Void... voids) {
try {
- OkHttpClient httpClient = HttpClient.create(getContext(), account);
AccountSettings settings = new AccountSettings(getContext(), account);
+ OkHttpClient httpClient = HttpClient.create(getContext(), settings);
JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri()));
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(journalEntity.getUid());
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionActivity.java
index f43479ae..edbe7fcf 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionActivity.java
@@ -54,8 +54,10 @@ public class CreateCollectionActivity extends AppCompatActivity {
setContentView(R.layout.activity_create_collection);
+ final EditText displayName = (EditText) findViewById(R.id.display_name);
if (info.type == CollectionInfo.Type.CALENDAR) {
setTitle(R.string.create_calendar);
+ displayName.setHint(R.string.create_calendar_display_name_hint);
final View colorSquare = findViewById(R.id.color);
colorSquare.setOnClickListener(new View.OnClickListener() {
@@ -75,6 +77,7 @@ public class CreateCollectionActivity extends AppCompatActivity {
});
} else {
setTitle(R.string.create_addressbook);
+ displayName.setHint(R.string.create_addressbook_display_name_hint);
final View colorGroup = findViewById(R.id.color_group);
colorGroup.setVisibility(View.GONE);
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java
index 5adb88aa..d23fc146 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.java
@@ -130,7 +130,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
// 1. find service ID
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
- authority = ContactsContract.AUTHORITY;
+ authority = App.getAddressBooksAuthority();
} else if (info.type == CollectionInfo.Type.CALENDAR) {
authority = CalendarContract.AUTHORITY;
} else {
@@ -142,7 +142,7 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
AccountSettings settings = new AccountSettings(getContext(), account);
HttpUrl principal = HttpUrl.get(settings.getUri());
- JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal);
+ JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal);
if (info.uid == null) {
info.uid = JournalManager.Journal.genUid();
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java
index b9fc1d00..75263ebf 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.java
@@ -8,6 +8,7 @@
package com.etesync.syncadapter.ui;
+import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
@@ -20,8 +21,10 @@ import android.content.Loader;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.PowerManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
+import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
@@ -50,7 +53,9 @@ import com.etesync.syncadapter.model.EntryEntity;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
+import com.etesync.syncadapter.resource.LocalAddressBook;
+import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
import lombok.Cleanup;
@@ -203,8 +208,10 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
if (logs != null)
report.append("\nLOGS:\n").append(logs).append("\n");
+ final Context context = getContext();
+
try {
- PackageManager pm = getContext().getPackageManager();
+ PackageManager pm = context.getPackageManager();
String installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
if (TextUtils.isEmpty(installedFrom))
installedFrom = "APK (directly)";
@@ -215,15 +222,31 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
App.log.log(Level.SEVERE, "Couldn't get software information", ex);
}
- report.append(
- "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))
+ report.append("CONFIGURATION\n");
+ // power saving
+ PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ if (powerManager != null && Build.VERSION.SDK_INT >= 23)
+ report.append("Power saving disabled: ")
+ .append(powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID) ? "yes" : "no")
+ .append("\n");
+ // permissions
+ for (String permission : new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS,
+ Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR,
+ PermissionsActivity.PERMISSION_READ_TASKS, PermissionsActivity.PERMISSION_WRITE_TASKS })
+ report.append(permission).append(" permission: ")
+ .append(ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")
+ .append("\n");
+ // system-wide sync settings
+ report.append("System-wide synchronization: ")
+ .append(ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually")
+ .append("\n");
+ // main accounts
+ AccountManager accountManager = AccountManager.get(context);
+ for (Account acct : accountManager.getAccountsByType(context.getString(R.string.account_type)))
try {
- AccountSettings settings = new AccountSettings(getContext(), acct);
+ AccountSettings settings = new AccountSettings(context, acct);
report.append("Account: ").append(acct.name).append("\n" +
- " Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" +
+ " Address book sync. interval: ").append(syncStatus(settings, context.getString(R.string.address_books_authority))).append("\n" +
" Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" +
" OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" +
" WiFi only: ").append(settings.getSyncWifiOnly());
@@ -235,10 +258,21 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
} catch(InvalidAccountException e) {
report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n");
}
+ // address book accounts
+ for (Account acct : accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)))
+ try {
+ LocalAddressBook addressBook = new LocalAddressBook(context, acct, null);
+ report.append("Address book account: ").append(acct.name).append("\n" +
+ " Main account: ").append(addressBook.getMainAccount()).append("\n" +
+ " URL: ").append(addressBook.getURL()).append("\n" +
+ " Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n");
+ } catch(ContactsStorageException e) {
+ report.append(acct).append(" is invalid: ").append(e.getMessage()).append("\n");
+ }
report.append("\n");
report.append("SQLITE DUMP\n");
- @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
+ @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
dbHelper.dump(report);
report.append("\n");
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java
index cf5bed55..20606422 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.java
@@ -119,7 +119,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
AccountSettings settings = new AccountSettings(getContext(), account);
HttpUrl principal = HttpUrl.get(settings.getUri());
- JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal);
+ JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal);
Crypto.CryptoManager crypto = new Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid);
journalManager.delete(new JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid));
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java
index 20908bb7..9bc3a4bb 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java
@@ -59,11 +59,7 @@ public class EditCollectionActivity extends CreateCollectionActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
- getMenuInflater().inflate(R.menu.activity_create_collection, menu);
- } else {
- getMenuInflater().inflate(R.menu.activity_edit_collection, menu);
- }
+ getMenuInflater().inflate(R.menu.activity_edit_collection, menu);
return true;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java
index a8191238..e61799ee 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.java
@@ -52,7 +52,7 @@ public class RemoveMemberFragment extends DialogFragment {
memberEmail = getArguments().getString(KEY_MEMBER);
try {
settings = new AccountSettings(getContext(), account);
- httpClient = HttpClient.create(getContext(), account);
+ httpClient = HttpClient.create(getContext(), settings);
} catch (InvalidAccountException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/StartupDialogFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/StartupDialogFragment.java
index e04cfa0c..4c39fdd1 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/StartupDialogFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/StartupDialogFragment.java
@@ -52,7 +52,7 @@ public class StartupDialogFragment extends DialogFragment {
// battery optimization whitelisting
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS)) {
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- if (!powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
+ if (powerManager != null && !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
dialogs.add(StartupDialogFragment.instantiate(Mode.BATTERY_OPTIMIZATIONS));
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java
index 1a117511..fb5f1d55 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java
@@ -193,19 +193,7 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh
}
public void onManageMembers(MenuItem item) {
- if (info.type.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setIcon(R.drawable.ic_info_dark)
- .setTitle(R.string.not_allowed_title)
- .setMessage(R.string.members_address_book_not_allowed)
- .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
-
- }
- }).create();
- dialog.show();
- } else if (info.version < 2) {
+ if (info.version < 2) {
AlertDialog dialog = new AlertDialog.Builder(this)
.setIcon(R.drawable.ic_info_dark)
.setTitle(R.string.not_allowed_title)
@@ -256,7 +244,7 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh
}
} else {
try {
- LocalAddressBook resource = new LocalAddressBook(account, getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI));
+ LocalAddressBook resource = LocalAddressBook.findByUid(ViewCollectionActivity.this, getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI), account, info.uid);
count = resource.count();
} catch (ContactsStorageException e) {
e.printStackTrace();
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java
index 6b06b39f..0ca5e770 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/ImportFragment.java
@@ -307,7 +307,7 @@ public class ImportFragment extends DialogFragment {
finishParsingFile(contacts.length);
ContentProviderClient provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI);
- LocalAddressBook localAddressBook = new LocalAddressBook(account, provider);
+ LocalAddressBook localAddressBook = LocalAddressBook.findByUid(getContext(), provider, account, info.uid);
for (Contact contact : contacts) {
try {
@@ -332,7 +332,7 @@ public class ImportFragment extends DialogFragment {
} catch (FileNotFoundException e) {
result.e = e;
return result;
- } catch (InvalidCalendarException | IOException e) {
+ } catch (InvalidCalendarException | IOException | ContactsStorageException e) {
result.e = e;
return result;
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java
index dfa42309..487a19e6 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.java
@@ -104,7 +104,7 @@ public class LocalContactImportFragment extends Fragment {
String accountType = cursor.getString(accountTypeIndex);
if (account == null || (!account.name.equals(accountName) || !account.type.equals(accountType))) {
account = new Account(accountName, accountType);
- localAddressBooks.add(new LocalAddressBook(account, provider));
+ localAddressBooks.add(new LocalAddressBook(getContext(), account, provider));
}
}
@@ -152,8 +152,9 @@ public class LocalContactImportFragment extends Fragment {
private ResultFragment.ImportResult importContacts(LocalAddressBook localAddressBook) {
ResultFragment.ImportResult result = new ResultFragment.ImportResult();
try {
- LocalAddressBook addressBook = new LocalAddressBook(account,
- getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI));
+ LocalAddressBook addressBook = LocalAddressBook.findByUid(getContext(),
+ getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI),
+ account, info.uid);
LocalContact[] localContacts = localAddressBook.getAll();
int total = localContacts.length;
progressDialog.setMax(total);
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java
index 163b0682..e3e9a45a 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.java
@@ -15,6 +15,8 @@ import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.provider.CalendarContract;
@@ -24,6 +26,7 @@ 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 com.etesync.syncadapter.AccountSettings;
import com.etesync.syncadapter.App;
@@ -39,7 +42,9 @@ import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalTaskList;
+import com.etesync.syncadapter.ui.DebugInfoActivity;
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration;
+import com.etesync.syncadapter.utils.AndroidCompat;
import java.util.logging.Level;
@@ -87,11 +92,23 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
@Override
public void onLoadFinished(Loader loader, Configuration config) {
- if (createAccount(config.userName, config)) {
- getActivity().setResult(Activity.RESULT_OK);
- getActivity().finish();
- } else {
+ try {
+ if (createAccount(config.userName, config)) {
+ getActivity().setResult(Activity.RESULT_OK);
+ getActivity().finish();
+ }
+ } catch (InvalidAccountException e) {
App.log.severe("Account creation failed!");
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.account_creation_failed)
+ .setIcon(R.drawable.ic_error_dark)
+ .setMessage(e.getLocalizedMessage())
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // dismiss
+ }
+ }).show();
}
dismissAllowingStateLoss();
@@ -145,17 +162,18 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
}
- protected boolean createAccount(String accountName, BaseConfigurationFinder.Configuration config) {
- Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
+ protected boolean createAccount(String accountName, BaseConfigurationFinder.Configuration config) throws InvalidAccountException {
+ Account account = new Account(accountName, App.getAccountType());
// create Android account
- Bundle userData = AccountSettings.initialUserData(config.url, config.userName);
- App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, userData });
+ App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, config.userName, config.url });
AccountManager accountManager = AccountManager.get(getContext());
- if (!accountManager.addAccountExplicitly(account, config.password, userData))
+ if (!accountManager.addAccountExplicitly(account, config.password, null))
return false;
+ AccountSettings.setUserData(accountManager, account, config.url, config.userName);
+
// add entries for account to service DB
App.log.log(Level.INFO, "Writing account configuration to database", config);
try {
@@ -171,9 +189,9 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV);
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
- settings.setSyncInterval(ContactsContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
+ settings.setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL);
} else {
- ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
+ ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0);
}
if (config.calDAV != null) {
@@ -195,6 +213,8 @@ public class SetupEncryptionFragment extends DialogFragment implements LoaderMan
} catch(InvalidAccountException e) {
App.log.log(Level.SEVERE, "Couldn't access account settings", e);
+ AndroidCompat.removeAccount(accountManager, account);
+ throw e;
}
return true;
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java
index 997f810c..c8953e9f 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.java
@@ -90,7 +90,7 @@ public class SetupUserInfoFragment extends DialogFragment {
protected SetupUserInfo.SetupUserInfoResult doInBackground(Account... accounts) {
try {
Crypto.CryptoManager cryptoManager;
- OkHttpClient httpClient = HttpClient.create(getContext(), account);
+ OkHttpClient httpClient = HttpClient.create(getContext(), settings);
UserInfoManager userInfoManager = new UserInfoManager(httpClient, HttpUrl.get(settings.getUri()));
UserInfoManager.UserInfo userInfo = userInfoManager.get(account.name);
diff --git a/app/src/main/java/com/etesync/syncadapter/utils/AndroidCompat.java b/app/src/main/java/com/etesync/syncadapter/utils/AndroidCompat.java
new file mode 100644
index 00000000..36148eba
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/utils/AndroidCompat.java
@@ -0,0 +1,16 @@
+package com.etesync.syncadapter.utils;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.os.Build;
+
+public class AndroidCompat {
+ public static void removeAccount (AccountManager accountManager, Account account) {
+ if (Build.VERSION.SDK_INT >=
+ Build.VERSION_CODES.LOLLIPOP_MR1) {
+ accountManager.removeAccountExplicitly(account);
+ } else {
+ accountManager.removeAccount(account, null, null);
+ }
+ }
+}
diff --git a/app/src/main/res/menu/caldav_actions.xml b/app/src/main/res/menu/caldav_actions.xml
index 4027b93f..59b30419 100644
--- a/app/src/main/res/menu/caldav_actions.xml
+++ b/app/src/main/res/menu/caldav_actions.xml
@@ -10,6 +10,6 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/carddav_actions.xml b/app/src/main/res/menu/carddav_actions.xml
new file mode 100644
index 00000000..883f2558
--- /dev/null
+++ b/app/src/main/res/menu/carddav_actions.xml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 1a60a2a3..f0f65127 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -57,7 +57,6 @@
Smazat účet
Opravdu smazat účet?
Všechny místní kopie adresáře, kalendářů a úkolů budou smazány.
- Vytvořit nový kalendář
Oprávnění pro kalendáře
Vyžádat oprávnění kalendáře
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 2fe0bc98..feba51e7 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -44,7 +44,6 @@
Slet konto
Ønsker du at slette konto?
Alle lokale kopier af addessebøger, kalendere og opgavelister vil blive slettet.
- Opret ny kalender
Kalenderadgange
Anmod om kalenderadgang
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index cb8a7f4e..7f515334 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -78,7 +78,6 @@
Konto löschen
Konto wirklich löschen?
Alle lokalen gespeicherten Kopien von Addressbüchern, Kalendern und Aufgabenlisten werden gelöscht.
- Neuen Kalender erstellen
Letzte Sammlung kann nicht gelöscht werden
Die letzte Sammlung kann nicht gelöscht werden, bitte erstellen Sie eine neue Sammlung wenn Sie diese löschen wollen.
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 82e46739..ba899fc8 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -57,7 +57,6 @@
Eliminar cuenta
¿Seguro que deseas eliminar la cuenta?
Todas las copias locales de tus contactos, calendarios y tareas serán eliminadas.
- Crear nuevo calendario
Permisos de calendario
Solicitar permisos sobre calendario
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 5dfe45ee..fa68e04a 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -57,7 +57,6 @@
Supprimer le compte
Voulez-vous vraiment supprimer le compte?
Toutes les copies locales des carnets d\'adresses, des calendriers et des listes de tâches seront supprimées.
- Créer un nouveau calendrier
Autorisations calendrier
Demande d\'autorisations d\'accéder au calendrier
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index e4817fd8..d3ebb1c6 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -57,7 +57,6 @@
Fiók törlése
Valóban törölni akarja a fiókot?
Az összes címjegyzék, naptár és feladatlista helyi példányai törölve lesznek.
- Új naptár létrehozása
Naptárengedély
Naptárhozzáférés igénylése
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 4fce9b26..ba3290bc 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -55,7 +55,6 @@
Elimina account
Cancellare l\'account?
Tutte le copie locali delle rubriche, dei calendari e degli elenchi attività verranno eliminate.
- Crea nuovo calendario
Permessi calendario
Richiesta autorizzazione al calendario
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 0e9f1dc6..e897feb1 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -57,7 +57,6 @@
アカウントを削除
アカウントを削除してもよろしいですか?
アドレス帳、カレンダー、タスクリストのローカルコピーがすべて削除されます。
- 新しいカレンダーを作成
カレンダー アクセス許可
カレンダー アクセス許可の要求
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 250e375f..b5276360 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -51,7 +51,6 @@
Account verwijderen
Account echt verwijderen?
Alle lokale kopieën van adresboeken, agenda\'s en taken worden verwijderd.
- Maak een nieuwe agenda
Agenda rechten
Agenda rechten verkrijgen
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 3d2a976a..ebf5f3da 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -57,7 +57,6 @@
Usuń konto
Naprawdę chcesz usunąć konto?
Wszystkie lokalne kopie książek adresowych, kalendarzy i list zadań zostaną usunięte.
- Stwórz nowy kalendarz
Uprawnienia kalendarza
Zezwól na uprawnienia kalendarza
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 6d7b4860..5830b1c9 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -57,7 +57,6 @@
Excluir conta
Deseja excluir a conta?
Todas as cópias locais dos livros de endereços, calendários e listas de tarefas serão excluídas.
- Criar novo calendário
Permissões do calendário
Solicitar permissão do calendário
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 617a55f9..0f69cef1 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -57,7 +57,6 @@
Обриши налог
Заиста обрисати налог?
Све локалне копије адресара, календара и листи задатака ће бити обрисане.
- Направи нови календар
Дозволе за календар
Захтевај дозволе за календар
diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml
index d8190eed..62de631c 100644
--- a/app/src/main/res/values-tr-rTR/strings.xml
+++ b/app/src/main/res/values-tr-rTR/strings.xml
@@ -44,7 +44,6 @@
Hesabı sil
Hesap gerçekten silinsin mi?
Rehber, takvim ve iş listelerinin tüm yerel kopyaları silinecektir.
- Yeni takvim oluştur
Takvim izinleri
Takvim izinleri iste
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 092eb91d..9116e656 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -57,7 +57,6 @@
删除账户
真的要删除账户吗?
所有通讯录、日历和任务列表的本机存储将被删除。
- 创建日历
日历权限
请求日历权限
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a061b759..bea03001 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,6 +11,11 @@
EteSync
+ com.etesync.syncadapter
+ com.etesync.syncadapter.address_book
+ EteSync Address book
+ com.etesync.syncadapter.addressbooks
+ Address books
Help
Manage accounts
Please wait …
@@ -93,7 +98,6 @@
Show Fingerprint
Really delete account?
All local copies of address books, calendars and task lists will be deleted.
- Create new calendar
Can\'t delete last collection
Deleting the last collection is not allowed, please create a new one if you\'d like to delete this one.
You can click on an item to view the collection. From there you can view the journal, import, and much more...
@@ -107,7 +111,6 @@
Only the owner of this collection (%s) is allowed to view its members.
Not Allowed
Only the owner of this collection (%s) is allowed to edit it.
- Sharing of address books is currently not supported.
Sharing of old-style journals is not allowed. In order to share this journal, create a new one, and copy its contents over using the \"import\" dialog. If you are experiencing any issues, please contact support.
@@ -164,6 +167,8 @@
Setting up encryption
Please wait, setting up encryption…
+ Account creation failed
+
Encryption Error
diff --git a/app/src/main/res/xml/account_authenticator.xml b/app/src/main/res/xml/account_authenticator.xml
index b0560f1d..fc71fa90 100644
--- a/app/src/main/res/xml/account_authenticator.xml
+++ b/app/src/main/res/xml/account_authenticator.xml
@@ -7,7 +7,7 @@
-->
+
+
diff --git a/app/src/main/res/xml/sync_address_books.xml b/app/src/main/res/xml/sync_address_books.xml
new file mode 100644
index 00000000..fea4886d
--- /dev/null
+++ b/app/src/main/res/xml/sync_address_books.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/xml/sync_contacts.xml b/app/src/main/res/xml/sync_contacts.xml
index 5d0e19fe..02f747e4 100644
--- a/app/src/main/res/xml/sync_contacts.xml
+++ b/app/src/main/res/xml/sync_contacts.xml
@@ -7,9 +7,9 @@
-->
+ android:userVisible="false"
+ android:isAlwaysSyncable="true" />
diff --git a/ical4android b/ical4android
index 2f6a94e8..49fbb684 160000
--- a/ical4android
+++ b/ical4android
@@ -1 +1 @@
-Subproject commit 2f6a94e85b9c0a375af8bdb08c8af82287c5fdc4
+Subproject commit 49fbb6846153fee5e5cbc71b77e94bef7629936d