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 @@ + android:title="@string/create_calendar"/> \ 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