1
0
mirror of https://github.com/etesync/android synced 2024-12-23 07:08:16 +00:00

Kotlin: more kotlin migration.

This commit is contained in:
Tom Hacohen 2019-01-05 11:44:14 +00:00
parent 4c4c94ca1c
commit 4d516c5fe1
36 changed files with 1529 additions and 1615 deletions

View File

@ -105,7 +105,7 @@ public class DavResourceFinderTest {
"</resourcetype>";
break;
}
App.log.info("Sending props: " + props);
App.Companion.getLog().info("Sending props: " + props);
return new MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +

View File

@ -1,357 +0,0 @@
/*
* 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;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.PeriodicSync;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.utils.Base64;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.logging.Level;
import at.bitfire.vcard4android.ContactsStorageException;
import at.bitfire.vcard4android.GroupMethod;
public class AccountSettings {
private final static int CURRENT_VERSION = 2;
private final static String
KEY_SETTINGS_VERSION = "version",
KEY_URI = "uri",
KEY_USERNAME = "user_name",
KEY_TOKEN = "auth_token",
KEY_ASYMMETRIC_PRIVATE_KEY = "asymmetric_private_key",
KEY_ASYMMETRIC_PUBLIC_KEY = "asymmetric_public_key",
KEY_WIFI_ONLY = "wifi_only", // sync on WiFi only (default: false)
KEY_WIFI_ONLY_SSID = "wifi_only_ssid"; // restrict sync to specific WiFi SSID
/**
* Time range limitation to the past [in days]
* value = null default value (DEFAULT_TIME_RANGE_PAST_DAYS)
* < 0 (-1) no limit
* >= 0 entries more than n days in the past won't be synchronized
*/
private final static String KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days";
private final static int DEFAULT_TIME_RANGE_PAST_DAYS = 90;
/* Whether DAVdroid sets the local calendar color to the value from service DB at every sync
value = null (not existing) true (default)
"0" false */
private final static String KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors";
/**
* Contact group method:
* value = null (not existing) groups as separate VCards (default)
* "CATEGORIES" groups are per-contact CATEGORIES
*/
private final static String KEY_CONTACT_GROUP_METHOD = "contact_group_method";
public final static long SYNC_INTERVAL_MANUALLY = -1;
final Context context;
final AccountManager accountManager;
final Account account;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public AccountSettings(@NonNull Context context, @NonNull Account account) throws InvalidAccountException {
this.context = context;
this.account = account;
accountManager = AccountManager.get(context);
synchronized (AccountSettings.class) {
String versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION);
if (versionStr == null)
throw new InvalidAccountException(account);
int version = 0;
try {
version = Integer.parseInt(versionStr);
} catch (NumberFormatException ignored) {
}
App.log.fine("Account " + account.name + " has version " + version + ", current version: " + CURRENT_VERSION);
if (version < CURRENT_VERSION)
update(version);
}
}
// XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work.
public static void setUserData(AccountManager accountManager, Account account, URI uri, String userName) {
accountManager.setUserData(account, KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
accountManager.setUserData(account, KEY_USERNAME, userName);
accountManager.setUserData(account, KEY_URI, uri.toString());
}
// authentication settings
public URI getUri() {
try {
return new URI(accountManager.getUserData(account, KEY_URI));
} catch (URISyntaxException e) {
return null;
}
}
public void setUri(@NonNull URI uri) {
accountManager.setUserData(account, KEY_URI, uri.toString());
}
public String getAuthToken() {
return accountManager.getUserData(account, KEY_TOKEN);
}
public void setAuthToken(@NonNull String token) {
accountManager.setUserData(account, KEY_TOKEN, token);
}
public Crypto.AsymmetricKeyPair getKeyPair() {
if (accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY) != null) {
byte[] pubkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY), Base64.NO_WRAP);
byte[] privkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY), Base64.NO_WRAP);
return new Crypto.AsymmetricKeyPair(privkey, pubkey);
}
return null;
}
public void setKeyPair(@NonNull Crypto.AsymmetricKeyPair keyPair) {
accountManager.setUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY, Base64.encodeToString(keyPair.getPublicKey(), Base64.NO_WRAP));
accountManager.setUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY, Base64.encodeToString(keyPair.getPrivateKey(), Base64.NO_WRAP));
}
public String username() {
return accountManager.getUserData(account, KEY_USERNAME);
}
public void username(@NonNull String userName) {
accountManager.setUserData(account, KEY_USERNAME, userName);
}
public String password() {
return accountManager.getPassword(account);
}
public void password(@NonNull String password) {
accountManager.setPassword(account, password);
}
// sync. settings
public Long getSyncInterval(@NonNull String authority) {
if (ContentResolver.getIsSyncable(account, authority) <= 0)
return null;
if (ContentResolver.getSyncAutomatically(account, authority)) {
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, authority);
if (syncs.isEmpty())
return SYNC_INTERVAL_MANUALLY;
else
return syncs.get(0).period;
} else
return SYNC_INTERVAL_MANUALLY;
}
public void setSyncInterval(@NonNull String authority, long seconds) {
if (seconds == SYNC_INTERVAL_MANUALLY) {
ContentResolver.setSyncAutomatically(account, authority, false);
} else {
ContentResolver.setSyncAutomatically(account, authority, true);
ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds);
}
}
public boolean getSyncWifiOnly() {
return accountManager.getUserData(account, KEY_WIFI_ONLY) != null;
}
public void setSyncWiFiOnly(boolean wiFiOnly) {
accountManager.setUserData(account, KEY_WIFI_ONLY, wiFiOnly ? "1" : null);
}
@Nullable
public String getSyncWifiOnlySSID() {
return accountManager.getUserData(account, KEY_WIFI_ONLY_SSID);
}
public void setSyncWifiOnlySSID(String ssid) {
accountManager.setUserData(account, KEY_WIFI_ONLY_SSID, ssid);
}
// CalDAV settings
public boolean getManageCalendarColors() {
return accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null;
}
public void setManageCalendarColors(boolean manage) {
accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, manage ? null : "0");
}
// CardDAV settings
@NonNull
public GroupMethod getGroupMethod() {
final String name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD);
return name != null ?
GroupMethod.valueOf(name) :
GroupMethod.GROUP_VCARDS;
}
public void setGroupMethod(@NonNull GroupMethod method) {
final String name = method == GroupMethod.GROUP_VCARDS ? null : method.name();
accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name);
}
// update from previous account settings
private void update(int fromVersion) {
int toVersion = CURRENT_VERSION;
App.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion);
try {
updateInner(fromVersion);
accountManager.setUserData(account, KEY_SETTINGS_VERSION, String.valueOf(toVersion));
} catch (Exception e) {
App.log.log(Level.SEVERE, "Couldn't update account settings", e);
}
}
private void updateInner(int fromVersion) throws ContactsStorageException {
if (fromVersion < 2) {
long affected = -1;
long newCount = -1;
ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
if (provider == null)
// no access to contacts provider
return;
// don't run syncs during the migration
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0);
ContentResolver.cancelSync(account, null);
try {
// get previous address book settings (including URL)
byte[] raw = ContactsContract.SyncState.get(provider, account);
if (raw == null)
App.log.info("No contacts sync state, ignoring account");
else {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(raw, 0, raw.length);
parcel.setDataPosition(0);
Bundle params = parcel.readBundle();
parcel.recycle();
String url = params.getString("url");
if (url == null)
App.log.info("No address book URL, ignoring account");
else {
// create new address book
CollectionInfo info = new CollectionInfo();
info.type = CollectionInfo.Type.ADDRESS_BOOK;
info.uid = url;
info.displayName = account.name;
App.log.log(Level.INFO, "Creating new address book account", url);
Account addressBookAccount = new Account(LocalAddressBook.accountName(account, info), App.getAddressBookAccountType());
if (!accountManager.addAccountExplicitly(addressBookAccount, null, null))
throw new ContactsStorageException("Couldn't create address book account");
LocalAddressBook.setUserData(accountManager, addressBookAccount, account, info.uid);
LocalAddressBook newAddressBook = new LocalAddressBook(context, addressBookAccount, provider);
// move contacts to new address book
App.log.info("Moving contacts from " + account + " to " + addressBookAccount);
ContentValues newAccount = new ContentValues(2);
newAccount.put(ContactsContract.RawContacts.ACCOUNT_NAME, addressBookAccount.name);
newAccount.put(ContactsContract.RawContacts.ACCOUNT_TYPE, addressBookAccount.type);
affected = provider.update(ContactsContract.RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(),
newAccount,
ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
new String[]{account.name, account.type});
App.log.info(affected + " contacts moved to new address book");
newCount = newAddressBook.count();
}
ContactsContract.SyncState.set(provider, account, null);
}
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't migrate contacts to new address book", e);
}
provider.release();
// request sync of new address book account
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 1);
setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL);
// Error handling
if ((affected != -1) && (affected != newCount)) {
NotificationHelper notificationHelper = new NotificationHelper(context, "account-migration", Constants.NOTIFICATION_ACCOUNT_UPDATE);
notificationHelper.setThrowable(new AccountMigrationException("Failed to upgrade account"));
notificationHelper.notify("Account upgrade failed", "upgrading account");
}
}
}
public static class AppUpdatedReceiver extends BroadcastReceiver {
@Override
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
public void onReceive(Context context, Intent intent) {
App.log.info("EteSync was updated, checking for AccountSettings version");
// peek into AccountSettings to initiate a possible migration
AccountManager accountManager = AccountManager.get(context);
for (Account account : accountManager.getAccountsByType(App.getAccountType()))
try {
App.log.info("Checking account " + account.name);
new AccountSettings(context, account);
} catch (InvalidAccountException e) {
App.log.log(Level.SEVERE, "Couldn't check for updated account settings", e);
}
}
}
public static class AccountMigrationException extends Exception {
public AccountMigrationException(String msg) {
super(msg);
}
}
}

View File

@ -0,0 +1,335 @@
/*
* 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
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.PeriodicSync
import android.os.Build
import android.os.Bundle
import android.os.Parcel
import android.os.RemoteException
import android.provider.ContactsContract
import com.etesync.syncadapter.journalmanager.Crypto
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.resource.LocalAddressBook
import com.etesync.syncadapter.utils.Base64
import java.net.URI
import java.net.URISyntaxException
import java.util.logging.Level
import at.bitfire.vcard4android.ContactsStorageException
import at.bitfire.vcard4android.GroupMethod
class AccountSettings @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Throws(InvalidAccountException::class)
constructor(internal val context: Context, internal val account: Account) {
internal val accountManager: AccountManager
// authentication settings
var uri: URI?
get() {
try {
return URI(accountManager.getUserData(account, KEY_URI))
} catch (e: URISyntaxException) {
return null
}
}
set(uri) = accountManager.setUserData(account, KEY_URI, uri.toString())
var authToken: String
get() = accountManager.getUserData(account, KEY_TOKEN)
set(token) = accountManager.setUserData(account, KEY_TOKEN, token)
var keyPair: Crypto.AsymmetricKeyPair?
get() {
if (accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY) != null) {
val pubkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY), Base64.NO_WRAP)
val privkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY), Base64.NO_WRAP)
return Crypto.AsymmetricKeyPair(privkey, pubkey)
}
return null
}
set(keyPair) {
accountManager.setUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY, Base64.encodeToString(keyPair?.publicKey, Base64.NO_WRAP))
accountManager.setUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY, Base64.encodeToString(keyPair?.privateKey, Base64.NO_WRAP))
}
val syncWifiOnly: Boolean
get() = accountManager.getUserData(account, KEY_WIFI_ONLY) != null
var syncWifiOnlySSID: String?
get() = accountManager.getUserData(account, KEY_WIFI_ONLY_SSID)
set(ssid) = accountManager.setUserData(account, KEY_WIFI_ONLY_SSID, ssid)
// CalDAV settings
var manageCalendarColors: Boolean
get() = accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null
set(manage) = accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0")
// CardDAV settings
var groupMethod: GroupMethod
get() {
val name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD)
return if (name != null)
GroupMethod.valueOf(name)
else
GroupMethod.GROUP_VCARDS
}
set(method) {
val name = if (method == GroupMethod.GROUP_VCARDS) null else method.name
accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name)
}
init {
accountManager = AccountManager.get(context)
synchronized(AccountSettings::class.java) {
val versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION)
?: throw InvalidAccountException(account)
var version = 0
try {
version = Integer.parseInt(versionStr)
} catch (ignored: NumberFormatException) {
}
App.log.fine("Account " + account.name + " has version " + version + ", current version: " + CURRENT_VERSION)
if (version < CURRENT_VERSION)
update(version)
}
}
fun username(): String {
return accountManager.getUserData(account, KEY_USERNAME)
}
fun username(userName: String) {
accountManager.setUserData(account, KEY_USERNAME, userName)
}
fun password(): String {
return accountManager.getPassword(account)
}
fun password(password: String) {
accountManager.setPassword(account, password)
}
// sync. settings
fun getSyncInterval(authority: String): Long? {
if (ContentResolver.getIsSyncable(account, authority) <= 0)
return null
if (ContentResolver.getSyncAutomatically(account, authority)) {
val syncs = ContentResolver.getPeriodicSyncs(account, authority)
return if (syncs.isEmpty())
SYNC_INTERVAL_MANUALLY
else
syncs[0].period
} else
return SYNC_INTERVAL_MANUALLY
}
fun setSyncInterval(authority: String, seconds: Long) {
if (seconds == SYNC_INTERVAL_MANUALLY) {
ContentResolver.setSyncAutomatically(account, authority, false)
} else {
ContentResolver.setSyncAutomatically(account, authority, true)
ContentResolver.addPeriodicSync(account, authority, Bundle(), seconds)
}
}
fun setSyncWiFiOnly(wiFiOnly: Boolean) {
accountManager.setUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null)
}
// update from previous account settings
private fun update(fromVersion: Int) {
val toVersion = CURRENT_VERSION
App.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion)
try {
updateInner(fromVersion)
accountManager.setUserData(account, KEY_SETTINGS_VERSION, toVersion.toString())
} catch (e: Exception) {
App.log.log(Level.SEVERE, "Couldn't update account settings", e)
}
}
@Throws(ContactsStorageException::class)
private fun updateInner(fromVersion: Int) {
if (fromVersion < 2) {
var affected: Long = -1
var newCount: Long = -1
val provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)
?: // no access to contacts provider
return
// don't run syncs during the migration
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0)
ContentResolver.setIsSyncable(account, App.addressBooksAuthority, 0)
ContentResolver.cancelSync(account, null)
try {
// get previous address book settings (including URL)
val raw = ContactsContract.SyncState.get(provider, account)
if (raw == null)
App.log.info("No contacts sync state, ignoring account")
else {
val parcel = Parcel.obtain()
parcel.unmarshall(raw, 0, raw.size)
parcel.setDataPosition(0)
val params = parcel.readBundle()
parcel.recycle()
val url = params.getString("url")
if (url == null)
App.log.info("No address book URL, ignoring account")
else {
// create new address book
val info = CollectionInfo()
info.type = CollectionInfo.Type.ADDRESS_BOOK
info.uid = url
info.displayName = account.name
App.log.log(Level.INFO, "Creating new address book account", url)
val addressBookAccount = Account(LocalAddressBook.accountName(account, info), App.addressBookAccountType)
if (!accountManager.addAccountExplicitly(addressBookAccount, null, null))
throw ContactsStorageException("Couldn't create address book account")
LocalAddressBook.setUserData(accountManager, addressBookAccount, account, info.uid)
val newAddressBook = LocalAddressBook(context, addressBookAccount, provider)
// move contacts to new address book
App.log.info("Moving contacts from $account to $addressBookAccount")
val newAccount = ContentValues(2)
newAccount.put(ContactsContract.RawContacts.ACCOUNT_NAME, addressBookAccount.name)
newAccount.put(ContactsContract.RawContacts.ACCOUNT_TYPE, addressBookAccount.type)
affected = provider.update(ContactsContract.RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(),
newAccount,
ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
arrayOf(account.name, account.type)).toLong()
App.log.info(affected.toString() + " contacts moved to new address book")
newCount = newAddressBook.count()
}
ContactsContract.SyncState.set(provider, account, null)
}
} catch (e: RemoteException) {
throw ContactsStorageException("Couldn't migrate contacts to new address book", e)
}
provider.release()
// request sync of new address book account
ContentResolver.setIsSyncable(account, App.addressBooksAuthority, 1)
setSyncInterval(App.addressBooksAuthority!!, Constants.DEFAULT_SYNC_INTERVAL.toLong())
// Error handling
if (affected != -1L && affected != newCount) {
val notificationHelper = NotificationHelper(context, "account-migration", Constants.NOTIFICATION_ACCOUNT_UPDATE)
notificationHelper.setThrowable(AccountMigrationException("Failed to upgrade account"))
notificationHelper.notify("Account upgrade failed", "upgrading account")
}
}
}
class AppUpdatedReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
App.log.info("EteSync was updated, checking for AccountSettings version")
// peek into AccountSettings to initiate a possible migration
val accountManager = AccountManager.get(context)
for (account in accountManager.getAccountsByType(App.accountType))
try {
App.log.info("Checking account " + account.name)
AccountSettings(context, account)
} catch (e: InvalidAccountException) {
App.log.log(Level.SEVERE, "Couldn't check for updated account settings", e)
}
}
}
class AccountMigrationException(msg: String) : Exception(msg)
companion object {
private val CURRENT_VERSION = 2
private val KEY_SETTINGS_VERSION = "version"
private val KEY_URI = "uri"
private val KEY_USERNAME = "user_name"
private val KEY_TOKEN = "auth_token"
private val KEY_ASYMMETRIC_PRIVATE_KEY = "asymmetric_private_key"
private val KEY_ASYMMETRIC_PUBLIC_KEY = "asymmetric_public_key"
private val KEY_WIFI_ONLY = "wifi_only"
// sync on WiFi only (default: false)
private val KEY_WIFI_ONLY_SSID = "wifi_only_ssid" // restrict sync to specific WiFi SSID
/**
* Time range limitation to the past [in days]
* value = null default value (DEFAULT_TIME_RANGE_PAST_DAYS)
* < 0 (-1) no limit
* >= 0 entries more than n days in the past won't be synchronized
*/
private val KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days"
private val DEFAULT_TIME_RANGE_PAST_DAYS = 90
/* Whether DAVdroid sets the local calendar color to the value from service DB at every sync
value = null (not existing) true (default)
"0" false */
private val KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors"
/**
* Contact group method:
* value = null (not existing) groups as separate VCards (default)
* "CATEGORIES" groups are per-contact CATEGORIES
*/
private val KEY_CONTACT_GROUP_METHOD = "contact_group_method"
val SYNC_INTERVAL_MANUALLY: Long = -1
// XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work.
fun setUserData(accountManager: AccountManager, account: Account, uri: URI, userName: String) {
accountManager.setUserData(account, KEY_SETTINGS_VERSION, CURRENT_VERSION.toString())
accountManager.setUserData(account, KEY_USERNAME, userName)
accountManager.setUserData(account, KEY_URI, uri.toString())
}
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.database.DatabaseUtils;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.NonNull;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.resource.LocalAddressBook;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
public class AccountUpdateService extends Service {
public static final String
ACTION_ACCOUNTS_UPDATED = "accountsUpdated";
private final IBinder binder = new InfoBinder();
private final Set<Long> runningRefresh = new HashSet<>();
private final List<WeakReference<RefreshingStatusListener>> refreshingStatusListeners = new LinkedList<>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
switch (action) {
case ACTION_ACCOUNTS_UPDATED:
cleanupAccounts();
break;
}
}
return START_NOT_STICKY;
}
/* BOUND SERVICE PART
for communicating with the activities
*/
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public interface RefreshingStatusListener {
void onDavRefreshStatusChanged(long id, boolean refreshing);
}
public class InfoBinder extends Binder {
public boolean isRefreshing(long id) {
return runningRefresh.contains(id);
}
public void addRefreshingStatusListener(@NonNull RefreshingStatusListener listener, boolean callImmediate) {
refreshingStatusListeners.add(new WeakReference<>(listener));
if (callImmediate)
for (long id : runningRefresh)
listener.onDavRefreshStatusChanged(id, true);
}
public void removeRefreshingStatusListener(@NonNull RefreshingStatusListener listener) {
for (Iterator<WeakReference<RefreshingStatusListener>> iterator = refreshingStatusListeners.iterator(); iterator.hasNext(); ) {
RefreshingStatusListener item = iterator.next().get();
if (listener.equals(item))
iterator.remove();
}
}
}
/* ACTION RUNNABLES
which actually do the work
*/
@SuppressLint("MissingPermission")
void cleanupAccounts() {
App.log.info("Cleaning up orphaned accounts");
List<String> accountNames = new LinkedList<>();
AccountManager am = AccountManager.get(this);
for (Account account : am.getAccountsByType(App.getAccountType())) {
accountNames.add(account.name);
}
EntityDataStore<Persistable> data = ((App) getApplication()).getData();
// 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(accountNames)).get().value();
}
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.Service
import android.content.Intent
import android.database.DatabaseUtils
import android.os.Binder
import android.os.IBinder
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.resource.LocalAddressBook
import java.lang.ref.WeakReference
import java.util.HashSet
import java.util.LinkedList
import java.util.logging.Level
import at.bitfire.vcard4android.ContactsStorageException
import io.requery.Persistable
import io.requery.sql.EntityDataStore
class AccountUpdateService : Service() {
private val binder = InfoBinder()
private val runningRefresh = HashSet<Long>()
private val refreshingStatusListeners = LinkedList<WeakReference<RefreshingStatusListener>>()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
val action = intent.action
when (action) {
ACTION_ACCOUNTS_UPDATED -> cleanupAccounts()
}
}
return Service.START_NOT_STICKY
}
/* BOUND SERVICE PART
for communicating with the activities
*/
override fun onBind(intent: Intent): IBinder? {
return binder
}
interface RefreshingStatusListener {
fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean)
}
inner class InfoBinder : Binder() {
fun isRefreshing(id: Long): Boolean {
return runningRefresh.contains(id)
}
fun addRefreshingStatusListener(listener: RefreshingStatusListener, callImmediate: Boolean) {
refreshingStatusListeners.add(WeakReference(listener))
if (callImmediate)
for (id in runningRefresh)
listener.onDavRefreshStatusChanged(id, true)
}
fun removeRefreshingStatusListener(listener: RefreshingStatusListener) {
val iterator = refreshingStatusListeners.iterator()
while (iterator.hasNext()) {
val item = iterator.next().get()
if (listener == item)
iterator.remove()
}
}
}
/* ACTION RUNNABLES
which actually do the work
*/
@SuppressLint("MissingPermission")
internal fun cleanupAccounts() {
App.log.info("Cleaning up orphaned accounts")
val accountNames = LinkedList<String>()
val am = AccountManager.get(this)
for (account in am.getAccountsByType(App.accountType)) {
accountNames.add(account.name)
}
val data = (application as App).data
// delete orphaned address book accounts
for (addrBookAccount in am.getAccountsByType(App.addressBookAccountType)) {
val addressBook = LocalAddressBook(this, addrBookAccount, null)
try {
if (!accountNames.contains(addressBook.mainAccount.name))
addressBook.delete()
} catch (e: ContactsStorageException) {
App.log.log(Level.SEVERE, "Couldn't get address book main account", e)
}
}
if (accountNames.isEmpty()) {
data.delete(ServiceEntity::class.java).get().value()
} else {
data.delete(ServiceEntity::class.java).where(ServiceEntity.ACCOUNT.notIn(accountNames)).get().value()
}
}
companion object {
val ACTION_ACCOUNTS_UPDATED = "accountsUpdated"
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.LinkedList;
import java.util.List;
public class AccountsChangedReceiver extends BroadcastReceiver {
protected static final List<OnAccountsUpdateListener> listeners = new LinkedList<>();
@Override
public void onReceive(Context context, Intent intent) {
if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
Intent serviceIntent = new Intent(context, AccountUpdateService.class);
serviceIntent.setAction(AccountUpdateService.ACTION_ACCOUNTS_UPDATED);
context.startService(serviceIntent);
for (OnAccountsUpdateListener listener : listeners)
listener.onAccountsUpdated(null);
}
}
public static void registerListener(OnAccountsUpdateListener listener, boolean callImmediately) {
listeners.add(listener);
if (callImmediately)
listener.onAccountsUpdated(null);
}
public static void unregisterListener(OnAccountsUpdateListener listener) {
listeners.remove(listener);
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import java.util.LinkedList
class AccountsChangedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION == intent.action) {
val serviceIntent = Intent(context, AccountUpdateService::class.java)
serviceIntent.action = AccountUpdateService.ACTION_ACCOUNTS_UPDATED
context.startService(serviceIntent)
for (listener in listeners)
listener.onAccountsUpdated(null)
}
}
companion object {
protected val listeners: MutableList<OnAccountsUpdateListener> = LinkedList()
fun registerListener(listener: OnAccountsUpdateListener, callImmediately: Boolean) {
listeners.add(listener)
if (callImmediately)
listener.onAccountsUpdated(null)
}
fun unregisterListener(listener: OnAccountsUpdateListener) {
listeners.remove(listener)
}
}
}

View File

@ -1,462 +0,0 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
import android.os.StrictMode;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.Toast;
import com.etesync.syncadapter.log.LogcatHandler;
import com.etesync.syncadapter.log.PlainTextFormatter;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.Models;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.model.Settings;
import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.ui.AccountsActivity;
import com.etesync.syncadapter.utils.HintManager;
import com.etesync.syncadapter.utils.LanguageUtils;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraMailSender;
import org.acra.annotation.AcraToast;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.meta.EntityModel;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import okhttp3.internal.tls.OkHostnameVerifier;
@AcraCore(buildConfigClass = BuildConfig.class,
logcatArguments = {"-t", "500", "-v", "time"})
@AcraMailSender(mailTo = "reports@etesync.com",
subject = R.string.crash_email_subject,
reportFileName = "ACRA-report.stacktrace.json")
@AcraToast(resText = R.string.crash_message,
length = Toast.LENGTH_LONG)
public class App extends Application {
public static final String
DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts",
LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage",
OVERRIDE_PROXY = "overrideProxy",
OVERRIDE_PROXY_HOST = "overrideProxyHost",
OVERRIDE_PROXY_PORT = "overrideProxyPort",
FORCE_LANGUAGE = "forceLanguage",
CHANGE_NOTIFICATION = "changeNotification";
public static final String OVERRIDE_PROXY_HOST_DEFAULT = "localhost";
public static final int OVERRIDE_PROXY_PORT_DEFAULT = 8118;
public static final String DEFAULT_LANGUAGE = "default";
public static Locale sDefaultLocacle = Locale.getDefault();
private static String appName;
private CustomCertManager certManager;
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
private static HostnameVerifier hostnameVerifier;
public final static Logger log = Logger.getLogger("syncadapter");
static {
at.bitfire.cert4android.Constants.log = Logger.getLogger("syncadapter.cert4android");
}
private static String accountType;
private static String addressBookAccountType;
private static String addressBooksAuthority;
public static String getAppName() {
return App.appName;
}
public CustomCertManager getCertManager() {
return this.certManager;
}
public static SSLSocketFactoryCompat getSslSocketFactoryCompat() {
return App.sslSocketFactoryCompat;
}
public static HostnameVerifier getHostnameVerifier() {
return App.hostnameVerifier;
}
public static String getAccountType() {
return App.accountType;
}
public static String getAddressBookAccountType() {
return App.addressBookAccountType;
}
public static String getAddressBooksAuthority() {
return App.addressBooksAuthority;
}
@Override
@SuppressLint("HardwareIds")
public void onCreate() {
super.onCreate();
reinitCertManager();
reinitLogger();
StrictMode.enableDefaults();
initPrefVersion();
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);
loadLanguage();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// The following line triggers the initialization of ACRA
ACRA.init(this);
}
private void loadLanguage() {
ServiceDB.OpenHelper serviceDB = new ServiceDB.OpenHelper(this);
String lang = new Settings(serviceDB.getReadableDatabase()).getString(App.FORCE_LANGUAGE, null);
if (lang != null && !lang.equals(DEFAULT_LANGUAGE)) {
LanguageUtils.INSTANCE.setLanguage(this, lang);
}
serviceDB.close();
}
public void reinitCertManager() {
if (BuildConfig.customCerts) {
if (certManager != null)
certManager.close();
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
Settings settings = new Settings(dbHelper.getReadableDatabase());
certManager = new CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false));
sslSocketFactoryCompat = new SSLSocketFactoryCompat(certManager);
hostnameVerifier = certManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
dbHelper.close();
}
}
public void reinitLogger() {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
Settings settings = new Settings(dbHelper.getReadableDatabase());
boolean logToFile = settings.getBoolean(LOG_TO_EXTERNAL_STORAGE, false),
logVerbose = logToFile || Log.isLoggable(log.getName(), Log.DEBUG);
App.log.info("Verbose logging: " + logVerbose);
// set logging level according to preferences
final Logger rootLogger = Logger.getLogger("");
rootLogger.setLevel(logVerbose ? Level.ALL : Level.INFO);
// remove all handlers and add our own logcat handler
rootLogger.setUseParentHandlers(false);
for (Handler handler : rootLogger.getHandlers())
rootLogger.removeHandler(handler);
rootLogger.addHandler(LogcatHandler.INSTANCE);
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
// log to external file according to preferences
if (logToFile) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder .setSmallIcon(R.drawable.ic_sd_storage_light)
.setLargeIcon(getLauncherBitmap(this))
.setContentTitle(getString(R.string.logging_davdroid_file_logging))
.setLocalOnly(true);
File dir = getExternalFilesDir(null);
if (dir != null)
try {
String fileName = new File(dir, "etesync-" + Process.myPid() + "-" +
DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString();
log.info("Logging to " + fileName);
FileHandler fileHandler = new FileHandler(fileName);
fileHandler.setFormatter(PlainTextFormatter.DEFAULT);
log.addHandler(fileHandler);
builder .setContentText(dir.getPath())
.setSubText(getString(R.string.logging_to_external_storage_warning))
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.logging_to_external_storage, dir.getPath())))
.setOngoing(true);
} catch (IOException e) {
log.log(Level.SEVERE, "Couldn't create external log file", e);
builder .setContentText(getString(R.string.logging_couldnt_create_file, e.getLocalizedMessage()))
.setCategory(NotificationCompat.CATEGORY_ERROR);
}
else
builder.setContentText(getString(R.string.logging_no_external_storage));
nm.notify(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING, builder.build());
} else
nm.cancel(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING);
dbHelper.close();
}
@Nullable
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static Bitmap getLauncherBitmap(@NonNull Context context) {
Bitmap bitmapLogo = null;
Drawable drawableLogo = ContextCompat.getDrawable(context, R.mipmap.ic_launcher);
if (drawableLogo instanceof BitmapDrawable)
bitmapLogo = ((BitmapDrawable)drawableLogo).getBitmap();
return bitmapLogo;
}
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 settings (logger/cert manager)");
App app = (App)context.getApplicationContext();
app.reinitLogger();
}
}
private EntityDataStore<Persistable> dataStore;
/**
* @return {@link EntityDataStore} single instance for the application.
* <p/>
* Note if you're using Dagger you can make this part of your application level module returning
* {@code @Provides @Singleton}.
*/
public EntityDataStore<Persistable> getData() {
if (dataStore == null) {
// override onUpgrade to handle migrating to a new version
DatabaseSource source = new MyDatabaseSource(this, Models.DEFAULT, 4);
Configuration configuration = source.getConfiguration();
dataStore = new EntityDataStore<>(configuration);
}
return dataStore;
}
private static class MyDatabaseSource extends DatabaseSource {
MyDatabaseSource(Context context, EntityModel entityModel, int version) {
super(context, entityModel, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, newVersion);
if (oldVersion < 3) {
db.execSQL("PRAGMA foreign_keys=OFF;");
db.execSQL("CREATE TABLE new_Journal (id integer primary key autoincrement not null, deleted boolean not null, encryptedKey varbinary(255), info varchar(255), owner varchar(255), service integer, serviceModel integer, uid varchar(64) not null, readOnly boolean default false, foreign key (serviceModel) references Service (id) on delete cascade);");
db.execSQL("CREATE TABLE new_Entry (id integer primary key autoincrement not null, content varchar(255), journal integer, uid varchar(64) not null, foreign key (journal) references new_Journal (id) on delete cascade);");
db.execSQL("INSERT INTO new_Journal SELECT id, deleted, encryptedKey, info, owner, service, serviceModel, uid, 0 from Journal;");
db.execSQL("INSERT INTO new_Entry SELECT id, content, journal, uid from Entry;");
db.execSQL("DROP TABLE Journal;");
db.execSQL("DROP TABLE Entry;");
db.execSQL("ALTER TABLE new_Journal RENAME TO Journal;");
db.execSQL("ALTER TABLE new_Entry RENAME TO Entry;");
// Add back indexes
db.execSQL("CREATE UNIQUE INDEX journal_unique_together on Journal (serviceModel, uid);");
db.execSQL("CREATE UNIQUE INDEX entry_unique_together on Entry (journal, uid);");
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
}
// update from previous account settings
private final static String PREF_VERSION = "version";
/** Init the preferences version of the app.
* This is used to initialise the first version if not alrady set. */
private void initPrefVersion() {
SharedPreferences prefs = getSharedPreferences("app", Context.MODE_PRIVATE);
if (prefs.getInt(PREF_VERSION, 0) == 0) {
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply();
}
}
private void update(int fromVersion) {
App.log.info("Updating from version " + fromVersion + " to " + BuildConfig.VERSION_CODE);
if (fromVersion < 6) {
EntityDataStore<Persistable> data = this.getData();
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
List<CollectionInfo> collections = readCollections(dbHelper);
for (CollectionInfo info : collections) {
JournalEntity journalEntity = new JournalEntity(data, info);
data.insert(journalEntity);
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(ServiceDB.Collections._TABLE, null, null);
db.close();
}
if (fromVersion < 7) {
/* Fix all of the etags to be non-null */
AccountManager am = AccountManager.get(this);
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|InvalidAccountException e) {
e.printStackTrace();
}
}
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) {
e.printStackTrace();
}
}
}
if (fromVersion < 10) {
HintManager.INSTANCE.setHintSeen(this, AccountsActivity.Companion.getHINT_ACCOUNT_ADD(), true);
}
if (fromVersion < 11) {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
migrateServices(dbHelper);
}
}
public static class AppUpdatedReceiver extends BroadcastReceiver {
@Override
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
public void onReceive(Context context, Intent intent) {
App.log.info("EteSync was updated, checking for app version");
App app = (App) context.getApplicationContext();
SharedPreferences prefs = app.getSharedPreferences("app", Context.MODE_PRIVATE);
int fromVersion = prefs.getInt(PREF_VERSION, 1);
app.update(fromVersion);
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply();
}
}
@NonNull
private List<CollectionInfo> readCollections(ServiceDB.OpenHelper dbHelper) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
List<CollectionInfo> collections = new LinkedList<>();
Cursor cursor = db.query(ServiceDB.Collections._TABLE, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
collections.add(CollectionInfo.fromDB(values));
}
db.close();
cursor.close();
return collections;
}
public void migrateServices(ServiceDB.OpenHelper dbHelper) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
EntityDataStore<Persistable> data = this.getData();
Cursor cursor = db.query(ServiceDB.Services._TABLE, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
ServiceEntity service = new ServiceEntity();
service.setAccount(values.getAsString(ServiceDB.Services.ACCOUNT_NAME));
service.setType(CollectionInfo.Type.valueOf(values.getAsString(ServiceDB.Services.SERVICE)));
data.insert(service);
for (JournalEntity journalEntity : data.select(JournalEntity.class).where(JournalEntity.SERVICE.eq(values.getAsLong(ServiceDB.Services.ID))).get()) {
journalEntity.setServiceModel(service);
data.update(journalEntity);
}
}
db.delete(ServiceDB.Services._TABLE, null, null);
db.close();
cursor.close();
}
}

View File

@ -0,0 +1,433 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Application
import android.content.BroadcastReceiver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.database.Cursor
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Process
import android.os.StrictMode
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.content.ContextCompat
import android.util.Log
import android.widget.Toast
import com.etesync.syncadapter.log.LogcatHandler
import com.etesync.syncadapter.log.PlainTextFormatter
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.Models
import com.etesync.syncadapter.model.ServiceDB
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.model.Settings
import com.etesync.syncadapter.resource.LocalAddressBook
import com.etesync.syncadapter.resource.LocalCalendar
import com.etesync.syncadapter.ui.AccountsActivity
import com.etesync.syncadapter.utils.HintManager
import com.etesync.syncadapter.utils.LanguageUtils
import org.acra.ACRA
import org.acra.annotation.AcraCore
import org.acra.annotation.AcraMailSender
import org.acra.annotation.AcraToast
import org.apache.commons.lang3.time.DateFormatUtils
import java.io.File
import java.io.IOException
import java.util.LinkedList
import java.util.Locale
import java.util.logging.FileHandler
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.Logger
import javax.net.ssl.HostnameVerifier
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.vcard4android.ContactsStorageException
import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource
import io.requery.meta.EntityModel
import io.requery.sql.Configuration
import io.requery.sql.EntityDataStore
import okhttp3.internal.tls.OkHostnameVerifier
@AcraCore(buildConfigClass = BuildConfig::class, logcatArguments = arrayOf("-t", "500", "-v", "time"))
@AcraMailSender(mailTo = "reports@etesync.com", subject = R.string.crash_email_subject, reportFileName = "ACRA-report.stacktrace.json")
@AcraToast(resText = R.string.crash_message, length = Toast.LENGTH_LONG)
class App : Application() {
var certManager: CustomCertManager? = null
private set
/**
* @return [EntityDataStore] single instance for the application.
*
*
* Note if you're using Dagger you can make this part of your application level module returning
* `@Provides @Singleton`.
*/
// override onUpgrade to handle migrating to a new version
val data: EntityDataStore<Persistable>
get() = initDataStore()
fun initDataStore(): EntityDataStore<Persistable> {
val source = MyDatabaseSource(this, Models.DEFAULT, 4)
val configuration = source.configuration
return EntityDataStore(configuration)
}
@SuppressLint("HardwareIds")
override fun onCreate() {
super.onCreate()
reinitCertManager()
reinitLogger()
StrictMode.enableDefaults()
initPrefVersion()
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)
loadLanguage()
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// The following line triggers the initialization of ACRA
ACRA.init(this)
}
private fun loadLanguage() {
val serviceDB = ServiceDB.OpenHelper(this)
val lang = Settings(serviceDB.readableDatabase).getString(App.FORCE_LANGUAGE, null)
if (lang != null && lang != DEFAULT_LANGUAGE) {
LanguageUtils.setLanguage(this, lang)
}
serviceDB.close()
}
fun reinitCertManager() {
if (BuildConfig.customCerts) {
if (certManager != null)
certManager!!.close()
val dbHelper = ServiceDB.OpenHelper(this)
val settings = Settings(dbHelper.readableDatabase)
certManager = CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false))
sslSocketFactoryCompat = SSLSocketFactoryCompat(certManager!!)
hostnameVerifier = certManager!!.hostnameVerifier(OkHostnameVerifier.INSTANCE)
dbHelper.close()
}
}
fun reinitLogger() {
val dbHelper = ServiceDB.OpenHelper(this)
val settings = Settings(dbHelper.readableDatabase)
val logToFile = settings.getBoolean(LOG_TO_EXTERNAL_STORAGE, false)
val logVerbose = logToFile || Log.isLoggable(log.name, Log.DEBUG)
App.log.info("Verbose logging: $logVerbose")
// set logging level according to preferences
val rootLogger = Logger.getLogger("")
rootLogger.level = if (logVerbose) Level.ALL else Level.INFO
// remove all handlers and add our own logcat handler
rootLogger.useParentHandlers = false
for (handler in rootLogger.handlers)
rootLogger.removeHandler(handler)
rootLogger.addHandler(LogcatHandler.INSTANCE)
val nm = NotificationManagerCompat.from(this)
// log to external file according to preferences
if (logToFile) {
val builder = NotificationCompat.Builder(this)
builder.setSmallIcon(R.drawable.ic_sd_storage_light)
.setLargeIcon(getLauncherBitmap(this))
.setContentTitle(getString(R.string.logging_davdroid_file_logging))
.setLocalOnly(true)
val dir = getExternalFilesDir(null)
if (dir != null)
try {
val fileName = File(dir, "etesync-" + Process.myPid() + "-" +
DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString()
log.info("Logging to $fileName")
val fileHandler = FileHandler(fileName)
fileHandler.formatter = PlainTextFormatter.DEFAULT
log.addHandler(fileHandler)
builder.setContentText(dir.path)
.setSubText(getString(R.string.logging_to_external_storage_warning))
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(NotificationCompat.BigTextStyle()
.bigText(getString(R.string.logging_to_external_storage, dir.path)))
.setOngoing(true)
} catch (e: IOException) {
log.log(Level.SEVERE, "Couldn't create external log file", e)
builder.setContentText(getString(R.string.logging_couldnt_create_file, e.localizedMessage))
.setCategory(NotificationCompat.CATEGORY_ERROR)
}
else
builder.setContentText(getString(R.string.logging_no_external_storage))
nm.notify(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING, builder.build())
} else
nm.cancel(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING)
dbHelper.close()
}
class ReinitSettingsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
log.info("Received broadcast: re-initializing settings (logger/cert manager)")
val app = context.applicationContext as App
app.reinitLogger()
}
companion object {
val ACTION_REINIT_SETTINGS = BuildConfig.APPLICATION_ID + ".REINIT_SETTINGS"
}
}
private class MyDatabaseSource internal constructor(context: Context, entityModel: EntityModel, version: Int) : DatabaseSource(context, entityModel, version) {
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
super.onUpgrade(db, oldVersion, newVersion)
if (oldVersion < 3) {
db.execSQL("PRAGMA foreign_keys=OFF;")
db.execSQL("CREATE TABLE new_Journal (id integer primary key autoincrement not null, deleted boolean not null, encryptedKey varbinary(255), info varchar(255), owner varchar(255), service integer, serviceModel integer, uid varchar(64) not null, readOnly boolean default false, foreign key (serviceModel) references Service (id) on delete cascade);")
db.execSQL("CREATE TABLE new_Entry (id integer primary key autoincrement not null, content varchar(255), journal integer, uid varchar(64) not null, foreign key (journal) references new_Journal (id) on delete cascade);")
db.execSQL("INSERT INTO new_Journal SELECT id, deleted, encryptedKey, info, owner, service, serviceModel, uid, 0 from Journal;")
db.execSQL("INSERT INTO new_Entry SELECT id, content, journal, uid from Entry;")
db.execSQL("DROP TABLE Journal;")
db.execSQL("DROP TABLE Entry;")
db.execSQL("ALTER TABLE new_Journal RENAME TO Journal;")
db.execSQL("ALTER TABLE new_Entry RENAME TO Entry;")
// Add back indexes
db.execSQL("CREATE UNIQUE INDEX journal_unique_together on Journal (serviceModel, uid);")
db.execSQL("CREATE UNIQUE INDEX entry_unique_together on Entry (journal, uid);")
db.execSQL("PRAGMA foreign_keys=ON;")
}
}
}
/** Init the preferences version of the app.
* This is used to initialise the first version if not alrady set. */
private fun initPrefVersion() {
val prefs = getSharedPreferences("app", Context.MODE_PRIVATE)
if (prefs.getInt(PREF_VERSION, 0) == 0) {
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply()
}
}
private fun update(fromVersion: Int) {
App.log.info("Updating from version " + fromVersion + " to " + BuildConfig.VERSION_CODE)
if (fromVersion < 6) {
val data = this.data
val dbHelper = ServiceDB.OpenHelper(this)
val collections = readCollections(dbHelper)
for (info in collections) {
val journalEntity = JournalEntity(data, info)
data.insert(journalEntity)
}
val db = dbHelper.writableDatabase
db.delete(ServiceDB.Collections._TABLE, null, null)
db.close()
}
if (fromVersion < 7) {
/* Fix all of the etags to be non-null */
val am = AccountManager.get(this)
for (account in am.getAccountsByType(App.accountType)) {
try {
// Generate account settings to make sure account is migrated.
AccountSettings(this, account)
val calendars = LocalCalendar.find(account, this.contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)!!,
LocalCalendar.Factory.INSTANCE, null, null) as Array<LocalCalendar>
for (calendar in calendars) {
calendar.fixEtags()
}
} catch (e: CalendarStorageException) {
e.printStackTrace()
} catch (e: InvalidAccountException) {
e.printStackTrace()
}
}
for (account in am.getAccountsByType(App.addressBookAccountType)) {
val addressBook = LocalAddressBook(this, account, this.contentResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI))
try {
addressBook.fixEtags()
} catch (e: ContactsStorageException) {
e.printStackTrace()
}
}
}
if (fromVersion < 10) {
HintManager.setHintSeen(this, AccountsActivity.HINT_ACCOUNT_ADD, true)
}
if (fromVersion < 11) {
val dbHelper = ServiceDB.OpenHelper(this)
migrateServices(dbHelper)
}
}
class AppUpdatedReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
App.log.info("EteSync was updated, checking for app version")
val app = context.applicationContext as App
val prefs = app.getSharedPreferences("app", Context.MODE_PRIVATE)
val fromVersion = prefs.getInt(PREF_VERSION, 1)
app.update(fromVersion)
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply()
}
}
private fun readCollections(dbHelper: ServiceDB.OpenHelper): List<CollectionInfo> {
val db = dbHelper.writableDatabase
val collections = LinkedList<CollectionInfo>()
val cursor = db.query(ServiceDB.Collections._TABLE, null, null, null, null, null, null)
while (cursor.moveToNext()) {
val values = ContentValues()
DatabaseUtils.cursorRowToContentValues(cursor, values)
collections.add(CollectionInfo.fromDB(values))
}
db.close()
cursor.close()
return collections
}
fun migrateServices(dbHelper: ServiceDB.OpenHelper) {
val db = dbHelper.readableDatabase
val data = this.data
val cursor = db.query(ServiceDB.Services._TABLE, null, null, null, null, null, null)
while (cursor.moveToNext()) {
val values = ContentValues()
DatabaseUtils.cursorRowToContentValues(cursor, values)
val service = ServiceEntity()
service.account = values.getAsString(ServiceDB.Services.ACCOUNT_NAME)
service.type = CollectionInfo.Type.valueOf(values.getAsString(ServiceDB.Services.SERVICE))
data.insert(service)
for (journalEntity in data.select(JournalEntity::class.java).where(JournalEntity.SERVICE.eq(values.getAsLong(ServiceDB.Services.ID))).get()) {
journalEntity.serviceModel = service
data.update(journalEntity)
}
}
db.delete(ServiceDB.Services._TABLE, null, null)
db.close()
cursor.close()
}
companion object {
val DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts"
val LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage"
val OVERRIDE_PROXY = "overrideProxy"
val OVERRIDE_PROXY_HOST = "overrideProxyHost"
val OVERRIDE_PROXY_PORT = "overrideProxyPort"
val FORCE_LANGUAGE = "forceLanguage"
val CHANGE_NOTIFICATION = "changeNotification"
val OVERRIDE_PROXY_HOST_DEFAULT = "localhost"
val OVERRIDE_PROXY_PORT_DEFAULT = 8118
val DEFAULT_LANGUAGE = "default"
var sDefaultLocacle = Locale.getDefault()
lateinit var appName: String
private set
var sslSocketFactoryCompat: SSLSocketFactoryCompat? = null
private set
var hostnameVerifier: HostnameVerifier? = null
private set
val log = Logger.getLogger("syncadapter")
init {
at.bitfire.cert4android.Constants.log = Logger.getLogger("syncadapter.cert4android")
}
lateinit var accountType: String
private set
lateinit var addressBookAccountType: String
private set
lateinit var addressBooksAuthority: String
private set
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun getLauncherBitmap(context: Context): Bitmap? {
var bitmapLogo: Bitmap? = null
val drawableLogo = ContextCompat.getDrawable(context, R.mipmap.ic_launcher)
if (drawableLogo is BitmapDrawable)
bitmapLogo = drawableLogo.bitmap
return bitmapLogo
}
// update from previous account settings
private val PREF_VERSION = "version"
}
}

View File

@ -1,186 +0,0 @@
/*
* 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;
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;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
public class HttpClient {
private static final OkHttpClient client = new OkHttpClient();
private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
private static final String userAgent;
static {
String date = new SimpleDateFormat("yyyy/MM/dd", Locale.US).format(new Date(BuildConfig.buildTime));
userAgent = App.getAppName() + "/" + BuildConfig.VERSION_NAME + " (" + date + "; okhttp3) Android/" + Build.VERSION.RELEASE;
}
private HttpClient() {
}
public static OkHttpClient create(@Nullable Context context, @NonNull final Logger logger, @Nullable String host, @NonNull String token) {
OkHttpClient.Builder builder = defaultBuilder(context, logger);
// use account settings for authentication
builder = addAuthentication(builder, host, token);
return builder.build();
}
public static OkHttpClient create(@Nullable Context context, @NonNull AccountSettings settings, @NonNull final Logger logger) {
return create(context, logger, settings.getUri().getHost(), settings.getAuthToken());
}
public static OkHttpClient create(@NonNull Context context, @NonNull Logger logger) {
return defaultBuilder(context, logger).build();
}
public static OkHttpClient create(@NonNull Context context, @NonNull AccountSettings settings) {
return create(context, settings, App.log);
}
public static OkHttpClient create(@Nullable Context context) {
return create(context, App.log);
}
public static OkHttpClient create(@Nullable Context context, @NonNull URI uri, String authToken) {
return create(context, App.log, uri.getHost(), authToken);
}
private static OkHttpClient.Builder defaultBuilder(@Nullable Context context, @NonNull final Logger logger) {
OkHttpClient.Builder builder = client.newBuilder();
// use MemorizingTrustManager to manage self-signed certificates
if (context != null) {
App app = (App) context.getApplicationContext();
if (App.getSslSocketFactoryCompat() != null && app.getCertManager() != null)
builder.sslSocketFactory(App.getSslSocketFactoryCompat(), app.getCertManager());
if (App.getHostnameVerifier() != null)
builder.hostnameVerifier(App.getHostnameVerifier());
}
// set timeouts
builder.connectTimeout(30, TimeUnit.SECONDS);
builder.writeTimeout(30, TimeUnit.SECONDS);
builder.readTimeout(120, TimeUnit.SECONDS);
// custom proxy support
if (context != null) {
SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(context);
try {
Settings settings = new Settings(dbHelper.getReadableDatabase());
if (settings.getBoolean(App.OVERRIDE_PROXY, false)) {
InetSocketAddress address = new InetSocketAddress(
settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT),
settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT)
);
Proxy proxy = new Proxy(Proxy.Type.HTTP, address);
builder.proxy(proxy);
App.log.log(Level.INFO, "Using proxy", proxy);
}
} catch (IllegalArgumentException | NullPointerException e) {
App.log.log(Level.SEVERE, "Can't set proxy, ignoring", e);
} finally {
dbHelper.close();
}
}
// add User-Agent to every request
builder.addNetworkInterceptor(userAgentInterceptor);
// add network logging, if requested
if (logger.isLoggable(Level.FINEST)) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
logger.finest(message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
}
return builder;
}
private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @Nullable String host, @NonNull String token) {
TokenAuthenticator authHandler = new TokenAuthenticator(host, token);
return builder.addNetworkInterceptor(authHandler);
}
private static class TokenAuthenticator implements Interceptor {
protected static final String
HEADER_AUTHORIZATION = "Authorization";
final String host, token;
private TokenAuthenticator(String host, String token) {
this.host = host;
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
/* Only add to the host we want. */
if ((host == null)
|| (request.url().host().equals(host))) {
if ((token != null)
&& (request.header(HEADER_AUTHORIZATION) == null)) {
request = request.newBuilder()
.header(HEADER_AUTHORIZATION, "Token " + token)
.build();
}
}
return chain.proceed(request);
}
}
static class UserAgentInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Locale locale = Locale.getDefault();
Request request = chain.request().newBuilder()
.header("User-Agent", userAgent)
.header("Accept-Language", locale.getLanguage() + "-" + locale.getCountry() + ", " + locale.getLanguage() + ";q=0.7, *;q=0.5")
.build();
return chain.proceed(request);
}
}
}

View File

@ -0,0 +1,165 @@
/*
* 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
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import android.os.Build
import com.etesync.syncadapter.model.ServiceDB
import com.etesync.syncadapter.model.Settings
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URI
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
object HttpClient {
private val client = OkHttpClient()
private val userAgentInterceptor = UserAgentInterceptor()
private val userAgent: String
init {
val date = SimpleDateFormat("yyyy/MM/dd", Locale.US).format(Date(BuildConfig.buildTime))
userAgent = App.appName + "/" + BuildConfig.VERSION_NAME + " (" + date + "; okhttp3) Android/" + Build.VERSION.RELEASE
}
fun create(context: Context?, logger: Logger, host: String?, token: String): OkHttpClient {
var builder = defaultBuilder(context, logger)
// use account settings for authentication
builder = addAuthentication(builder, host, token)
return builder.build()
}
@JvmOverloads
fun create(context: Context?, settings: AccountSettings, logger: Logger = App.log): OkHttpClient {
return create(context, logger, settings.uri!!.host, settings.authToken)
}
@JvmOverloads
fun create(context: Context, logger: Logger = App.log): OkHttpClient {
return defaultBuilder(context, logger).build()
}
fun create(context: Context?, uri: URI, authToken: String): OkHttpClient {
return create(context, App.log, uri.host, authToken)
}
private fun defaultBuilder(context: Context?, logger: Logger): OkHttpClient.Builder {
val builder = client.newBuilder()
// use MemorizingTrustManager to manage self-signed certificates
if (context != null) {
val app = context.applicationContext as App
if (App.sslSocketFactoryCompat != null && app.certManager != null)
builder.sslSocketFactory(App.sslSocketFactoryCompat!!, app.certManager!!)
if (App.hostnameVerifier != null)
builder.hostnameVerifier(App.hostnameVerifier!!)
}
// set timeouts
builder.connectTimeout(30, TimeUnit.SECONDS)
builder.writeTimeout(30, TimeUnit.SECONDS)
builder.readTimeout(120, TimeUnit.SECONDS)
// custom proxy support
if (context != null) {
val dbHelper = ServiceDB.OpenHelper(context)
try {
val settings = Settings(dbHelper.readableDatabase)
if (settings.getBoolean(App.OVERRIDE_PROXY, false)) {
val address = InetSocketAddress(
settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT),
settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT)
)
val proxy = Proxy(Proxy.Type.HTTP, address)
builder.proxy(proxy)
App.log.log(Level.INFO, "Using proxy", proxy)
}
} catch (e: IllegalArgumentException) {
App.log.log(Level.SEVERE, "Can't set proxy, ignoring", e)
} catch (e: NullPointerException) {
App.log.log(Level.SEVERE, "Can't set proxy, ignoring", e)
} finally {
dbHelper.close()
}
}
// add User-Agent to every request
builder.addNetworkInterceptor(userAgentInterceptor)
// add network logging, if requested
if (logger.isLoggable(Level.FINEST)) {
val loggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> logger.finest(message) })
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
}
return builder
}
private fun addAuthentication(builder: OkHttpClient.Builder, host: String?, token: String): OkHttpClient.Builder {
val authHandler = TokenAuthenticator(host, token)
return builder.addNetworkInterceptor(authHandler)
}
private class TokenAuthenticator internal constructor(internal val host: String?, internal val token: String?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
/* Only add to the host we want. */
if (host == null || request.url().host() == host) {
if (token != null && request.header(HEADER_AUTHORIZATION) == null) {
request = request.newBuilder()
.header(HEADER_AUTHORIZATION, "Token $token")
.build()
}
}
return chain.proceed(request)
}
companion object {
protected val HEADER_AUTHORIZATION = "Authorization"
}
}
internal class UserAgentInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val locale = Locale.getDefault()
val request = chain.request().newBuilder()
.header("User-Agent", userAgent)
.header("Accept-Language", locale.language + "-" + locale.country + ", " + locale.language + ";q=0.7, *;q=0.5")
.build()
return chain.proceed(request)
}
}
}

View File

@ -6,14 +6,8 @@
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter;
package com.etesync.syncadapter
import android.accounts.Account;
import android.accounts.Account
public class InvalidAccountException extends Exception {
public InvalidAccountException(Account account) {
super("Invalid account: " + account);
}
}
class InvalidAccountException(account: Account) : Exception("Invalid account: $account")

View File

@ -1,165 +0,0 @@
package com.etesync.syncadapter;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.ui.AccountSettingsActivity;
import com.etesync.syncadapter.ui.DebugInfoActivity;
import com.etesync.syncadapter.ui.WebViewActivity;
import java.util.logging.Level;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.vcard4android.ContactsStorageException;
public class NotificationHelper {
private static final String CHANNEL_ID = "EteSync_default";
final NotificationManagerCompat notificationManager;
final Context context;
final String notificationTag;
final int notificationId;
Intent detailsIntent;
int messageString;
public Intent getDetailsIntent() {
return detailsIntent;
}
private Throwable throwable = null;
public NotificationHelper(Context context, String notificationTag, int notificationId) {
this.notificationManager = NotificationManagerCompat.from(context);
this.context = context;
this.notificationTag = notificationTag;
this.notificationId = notificationId;
}
public void setThrowable(Throwable e) {
throwable = e;
if (e instanceof Exceptions.UnauthorizedException) {
App.log.log(Level.SEVERE, "Not authorized anymore", e);
messageString = R.string.sync_error_unauthorized;
} else if (e instanceof Exceptions.UserInactiveException) {
App.log.log(Level.SEVERE, "User inactive");
messageString = R.string.sync_error_user_inactive;
} else if (e instanceof Exceptions.ServiceUnavailableException) {
App.log.log(Level.SEVERE, "Service unavailable");
messageString = R.string.sync_error_unavailable;
} else if (e instanceof Exceptions.HttpException) {
App.log.log(Level.SEVERE, "HTTP Exception during sync", e);
messageString = R.string.sync_error_http_dav;
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException || e instanceof SQLiteException) {
App.log.log(Level.SEVERE, "Couldn't access local storage", e);
messageString = R.string.sync_error_local_storage;
} else if (e instanceof Exceptions.IntegrityException) {
App.log.log(Level.SEVERE, "Integrity error", e);
messageString = R.string.sync_error_integrity;
} else {
App.log.log(Level.SEVERE, "Unknown sync error", e);
messageString = R.string.sync_error;
}
detailsIntent = new Intent(context, NotificationHandlerActivity.class);
detailsIntent.putExtra(DebugInfoActivity.Companion.getKEY_THROWABLE(), e);
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + notificationTag));
}
public void notify(String title, String state) {
String message = context.getString(messageString, state);
notify(title, message, null, detailsIntent);
}
public void notify(String title, String content, String bigText, Intent intent) {
notify(title, content, bigText, intent, -1);
}
public void notify(String title, String content, String bigText, Intent intent, int icon) {
createNotificationChannel();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
String category =
throwable == null ?
NotificationCompat.CATEGORY_STATUS : NotificationCompat.CATEGORY_ERROR;
if (icon == -1) {
//Check if error was configured
if (throwable == null) {
icon = R.drawable.ic_sync_dark;
} else {
icon = R.drawable.ic_error_light;
}
}
builder.setLargeIcon(App.getLauncherBitmap(context))
.setContentTitle(title)
.setContentText(content)
.setChannelId(CHANNEL_ID)
.setAutoCancel(true)
.setCategory(category)
.setSmallIcon(icon)
.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
;
if (bigText != null) builder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(bigText));
notificationManager.notify(notificationTag, notificationId, builder.build());
}
public void cancel() {
notificationManager.cancel(notificationTag, notificationId);
}
public static class NotificationHandlerActivity extends Activity {
@Override
public void onCreate(Bundle savedBundle) {
super.onCreate(savedBundle);
Bundle extras = getIntent().getExtras();
Exception e = (Exception) extras.get(DebugInfoActivity.Companion.getKEY_THROWABLE());
Intent detailsIntent;
if (e instanceof Exceptions.UnauthorizedException) {
detailsIntent = new Intent(this, AccountSettingsActivity.class);
} else if (e instanceof Exceptions.UserInactiveException) {
WebViewActivity.Companion.openUrl(this, Constants.dashboard);
return;
} else if (e instanceof AccountSettings.AccountMigrationException) {
WebViewActivity.Companion.openUrl(this, Constants.faqUri.buildUpon().encodedFragment("account-migration-error").build());
return;
} else {
detailsIntent = new Intent(this, DebugInfoActivity.class);
}
detailsIntent.putExtras(getIntent().getExtras());
startActivity(detailsIntent);
}
@Override
public void onStop() {
super.onStop();
finish();
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = context.getString(R.string.notification_channel_name);
NotificationChannel channel =
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}

View File

@ -0,0 +1,155 @@
package com.etesync.syncadapter
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import com.etesync.syncadapter.journalmanager.Exceptions
import com.etesync.syncadapter.ui.AccountSettingsActivity
import com.etesync.syncadapter.ui.DebugInfoActivity
import com.etesync.syncadapter.ui.WebViewActivity
import java.util.logging.Level
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.vcard4android.ContactsStorageException
class NotificationHelper(internal val context: Context, internal val notificationTag: String, internal val notificationId: Int) {
internal val notificationManager: NotificationManagerCompat
lateinit var detailsIntent: Intent
internal set
internal var messageString: Int = 0
private var throwable: Throwable? = null
init {
this.notificationManager = NotificationManagerCompat.from(context)
}
fun setThrowable(e: Throwable) {
throwable = e
if (e is Exceptions.UnauthorizedException) {
App.log.log(Level.SEVERE, "Not authorized anymore", e)
messageString = R.string.sync_error_unauthorized
} else if (e is Exceptions.UserInactiveException) {
App.log.log(Level.SEVERE, "User inactive")
messageString = R.string.sync_error_user_inactive
} else if (e is Exceptions.ServiceUnavailableException) {
App.log.log(Level.SEVERE, "Service unavailable")
messageString = R.string.sync_error_unavailable
} else if (e is Exceptions.HttpException) {
App.log.log(Level.SEVERE, "HTTP Exception during sync", e)
messageString = R.string.sync_error_http_dav
} else if (e is CalendarStorageException || e is ContactsStorageException || e is SQLiteException) {
App.log.log(Level.SEVERE, "Couldn't access local storage", e)
messageString = R.string.sync_error_local_storage
} else if (e is Exceptions.IntegrityException) {
App.log.log(Level.SEVERE, "Integrity error", e)
messageString = R.string.sync_error_integrity
} else {
App.log.log(Level.SEVERE, "Unknown sync error", e)
messageString = R.string.sync_error
}
detailsIntent = Intent(context, NotificationHandlerActivity::class.java)
detailsIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e)
detailsIntent.data = Uri.parse("uri://" + javaClass.name + "/" + notificationTag)
}
fun notify(title: String, state: String) {
val message = context.getString(messageString, state)
notify(title, message, null, detailsIntent)
}
@JvmOverloads
fun notify(title: String, content: String, bigText: String?, intent: Intent, icon: Int = -1) {
var icon = icon
createNotificationChannel()
val builder = NotificationCompat.Builder(context)
val category = if (throwable == null)
NotificationCompat.CATEGORY_STATUS
else
NotificationCompat.CATEGORY_ERROR
if (icon == -1) {
//Check if error was configured
if (throwable == null) {
icon = R.drawable.ic_sync_dark
} else {
icon = R.drawable.ic_error_light
}
}
builder.setLargeIcon(App.getLauncherBitmap(context))
.setContentTitle(title)
.setContentText(content)
.setChannelId(CHANNEL_ID)
.setAutoCancel(true)
.setCategory(category)
.setSmallIcon(icon)
.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
if (bigText != null)
builder.setStyle(NotificationCompat.BigTextStyle()
.bigText(bigText))
notificationManager.notify(notificationTag, notificationId, builder.build())
}
fun cancel() {
notificationManager.cancel(notificationTag, notificationId)
}
class NotificationHandlerActivity : Activity() {
public override fun onCreate(savedBundle: Bundle?) {
super.onCreate(savedBundle)
val extras = intent.extras
val e = extras!!.get(DebugInfoActivity.KEY_THROWABLE) as Exception
val detailsIntent: Intent
if (e is Exceptions.UnauthorizedException) {
detailsIntent = Intent(this, AccountSettingsActivity::class.java)
} else if (e is Exceptions.UserInactiveException) {
WebViewActivity.openUrl(this, Constants.dashboard)
return
} else if (e is AccountSettings.AccountMigrationException) {
WebViewActivity.openUrl(this, Constants.faqUri.buildUpon().encodedFragment("account-migration-error").build())
return
} else {
detailsIntent = Intent(this, DebugInfoActivity::class.java)
}
detailsIntent.putExtras(intent.extras!!)
startActivity(detailsIntent)
}
public override fun onStop() {
super.onStop()
finish()
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context.getString(R.string.notification_channel_name)
val channel = NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW)
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager!!.createNotificationChannel(channel)
}
}
companion object {
private val CHANNEL_ID = "EteSync_default"
}
}

View File

@ -1,37 +0,0 @@
/*
* 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;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import com.etesync.syncadapter.resource.LocalTaskList;
public class PackageChangedReceiver extends BroadcastReceiver {
@Override
@SuppressLint("MissingPermission")
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction()) ||
Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(intent.getAction()))
updateTaskSync(context);
}
static void updateTaskSync(@NonNull Context context) {
boolean tasksInstalled = LocalTaskList.tasksProviderAvailable(context);
App.log.info("Package (un)installed; OpenTasks provider now available = " + tasksInstalled);
// check all accounts and (de)activate OpenTasks if a CalDAV service is defined
// FIXME: Do something if we ever bring back tasks.
}
}

View File

@ -0,0 +1,37 @@
/*
* 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
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.etesync.syncadapter.resource.LocalTaskList
class PackageChangedReceiver : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_PACKAGE_ADDED == intent.action || Intent.ACTION_PACKAGE_FULLY_REMOVED == intent.action)
updateTaskSync(context)
}
companion object {
internal fun updateTaskSync(context: Context) {
val tasksInstalled = LocalTaskList.tasksProviderAvailable(context)
App.log.info("Package (un)installed; OpenTasks provider now available = $tasksInstalled")
// check all accounts and (de)activate OpenTasks if a CalDAV service is defined
// FIXME: Do something if we ever bring back tasks.
}
}
}

View File

@ -1,168 +0,0 @@
/*
* 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;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
public class SSLSocketFactoryCompat extends SSLSocketFactory {
private SSLSocketFactory delegate;
// Android 5.0+ (API level21) provides reasonable default settings
// but it still allows SSLv3
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
static String protocols[] = null, cipherSuites[] = null;
static {
try {
SSLSocket socket = (SSLSocket)SSLSocketFactory.getDefault().createSocket();
if (socket != null) {
/* set reasonable protocol versions */
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
// - remove all SSL versions (especially SSLv3) because they're insecure now
List<String> protocols = new LinkedList<>();
for (String protocol : socket.getSupportedProtocols())
if (!protocol.toUpperCase(Locale.US).contains("SSL"))
protocols.add(protocol);
App.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]);
/* set up reasonable cipher suites */
// choose known secure cipher suites
List<String> allowedCiphers = Arrays.asList(
// first priority
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
// second priority
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
// compat
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
);
List<String> availableCiphers = Arrays.asList(socket.getSupportedCipherSuites());
App.log.info("Available cipher suites: " + TextUtils.join(", ", availableCiphers));
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus
* disabling ciphers which are enabled by default, but have become unsecure), but for
* the security level of DAVdroid and maximum compatibility, disabling of insecure
* ciphers should be a server-side task */
// for the final set of enabled ciphers, take the ciphers enabled by default, ...
HashSet<String> enabledCiphers = new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites()));
App.log.info("Cipher suites enabled by default: " + TextUtils.join(", ", enabledCiphers));
// ... add explicitly allowed ciphers ...
enabledCiphers.addAll(allowedCiphers);
// ... and keep only those which are actually available
enabledCiphers.retainAll(availableCiphers);
App.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers));
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]);
socket.close();
}
} catch (IOException e) {
App.log.severe("Couldn't determine default TLS settings");
}
}
public SSLSocketFactoryCompat(@NonNull X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new X509TrustManager[] { trustManager }, null);
delegate = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
private void upgradeTLS(SSLSocket ssl) {
if (protocols != null)
ssl.setEnabledProtocols(protocols);
if (cipherSuites != null)
ssl.setEnabledCipherSuites(cipherSuites);
}
@Override
public String[] getDefaultCipherSuites() {
return cipherSuites;
}
@Override
public String[] getSupportedCipherSuites() {
return cipherSuites;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
Socket ssl = delegate.createSocket(s, host, port, autoClose);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket)ssl);
return ssl;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
Socket ssl = delegate.createSocket(host, port);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket)ssl);
return ssl;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
Socket ssl = delegate.createSocket(host, port, localHost, localPort);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket)ssl);
return ssl;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket ssl = delegate.createSocket(host, port);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket)ssl);
return ssl;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket ssl = delegate.createSocket(address, port, localAddress, localPort);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket)ssl);
return ssl;
}
}

View File

@ -0,0 +1,171 @@
/*
* 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
import android.text.TextUtils
import java.io.IOException
import java.net.InetAddress
import java.net.Socket
import java.security.GeneralSecurityException
import java.util.Arrays
import java.util.HashSet
import java.util.LinkedList
import java.util.Locale
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory() {
private var delegate: SSLSocketFactory? = null
init {
try {
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(trustManager), null)
delegate = sslContext.socketFactory
} catch (e: GeneralSecurityException) {
throw AssertionError() // The system has no TLS. Just give up.
}
}
private fun upgradeTLS(ssl: SSLSocket) {
if (protocols != null)
ssl.enabledProtocols = protocols
if (cipherSuites != null)
ssl.enabledCipherSuites = cipherSuites
}
override fun getDefaultCipherSuites(): Array<String>? {
return cipherSuites
}
override fun getSupportedCipherSuites(): Array<String>? {
return cipherSuites
}
@Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
val ssl = delegate!!.createSocket(s, host, port, autoClose)
if (ssl is SSLSocket)
upgradeTLS(ssl)
return ssl
}
@Throws(IOException::class)
override fun createSocket(host: String, port: Int): Socket {
val ssl = delegate!!.createSocket(host, port)
if (ssl is SSLSocket)
upgradeTLS(ssl)
return ssl
}
@Throws(IOException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket {
val ssl = delegate!!.createSocket(host, port, localHost, localPort)
if (ssl is SSLSocket)
upgradeTLS(ssl)
return ssl
}
@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket {
val ssl = delegate!!.createSocket(host, port)
if (ssl is SSLSocket)
upgradeTLS(ssl)
return ssl
}
@Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket {
val ssl = delegate!!.createSocket(address, port, localAddress, localPort)
if (ssl is SSLSocket)
upgradeTLS(ssl)
return ssl
}
companion object {
// Android 5.0+ (API level21) provides reasonable default settings
// but it still allows SSLv3
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
internal var protocols: Array<String>? = null
internal var cipherSuites: Array<String>? = null
init {
try {
val socket = SSLSocketFactory.getDefault().createSocket() as SSLSocket
if (socket != null) {
/* set reasonable protocol versions */
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
// - remove all SSL versions (especially SSLv3) because they're insecure now
val protocols = LinkedList<String>()
for (protocol in socket.supportedProtocols)
if (!protocol.toUpperCase(Locale.US).contains("SSL"))
protocols.add(protocol)
App.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols))
SSLSocketFactoryCompat.protocols = protocols.toTypedArray()
/* set up reasonable cipher suites */
// choose known secure cipher suites
val allowedCiphers = Arrays.asList(
// first priority
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
// second priority
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
// compat
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
)
val availableCiphers = Arrays.asList(*socket.supportedCipherSuites)
App.log.info("Available cipher suites: " + TextUtils.join(", ", availableCiphers))
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus
* disabling ciphers which are enabled by default, but have become unsecure), but for
* the security level of DAVdroid and maximum compatibility, disabling of insecure
* ciphers should be a server-side task */
// for the final set of enabled ciphers, take the ciphers enabled by default, ...
val enabledCiphers = HashSet(Arrays.asList(*socket.enabledCipherSuites))
App.log.info("Cipher suites enabled by default: " + TextUtils.join(", ", enabledCiphers))
// ... add explicitly allowed ciphers ...
enabledCiphers.addAll(allowedCiphers)
// ... and keep only those which are actually available
enabledCiphers.retainAll(availableCiphers)
App.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers))
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toTypedArray()
socket.close()
}
} catch (e: IOException) {
App.log.severe("Couldn't determine default TLS settings")
}
}
}
}

View File

@ -75,7 +75,7 @@ public class ServiceDB {
@Override
public void onCreate(SQLiteDatabase db) {
App.log.info("Creating database " + db.getPath());
App.Companion.getLog().info("Creating database " + db.getPath());
db.execSQL("CREATE TABLE " + Settings._TABLE + "(" +
Settings.NAME + " TEXT NOT NULL," +

View File

@ -73,7 +73,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
AccountManager accountManager = AccountManager.get(context);
List<LocalAddressBook> result = new LinkedList<>();
for (Account account : accountManager.getAccountsByType(App.getAddressBookAccountType())) {
for (Account account : accountManager.getAccountsByType(App.Companion.getAddressBookAccountType())) {
LocalAddressBook addressBook = new LocalAddressBook(context, account, provider);
if (mainAccount == null || addressBook.getMainAccount().equals(mainAccount))
result.add(addressBook);
@ -85,7 +85,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
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())) {
for (Account account : accountManager.getAccountsByType(App.Companion.getAddressBookAccountType())) {
LocalAddressBook addressBook = new LocalAddressBook(context, account, provider);
if (addressBook.getURL().equals(uid) && (mainAccount == null || addressBook.getMainAccount().equals(mainAccount)))
return addressBook;
@ -98,7 +98,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
CollectionInfo info = journalEntity.getInfo();
AccountManager accountManager = AccountManager.get(context);
Account account = new Account(accountName(mainAccount, info), App.getAddressBookAccountType());
Account account = new Account(accountName(mainAccount, info), App.Companion.getAddressBookAccountType());
if (!accountManager.addAccountExplicitly(account, null, null))
throw new ContactsStorageException("Couldn't create address book account");
@ -129,7 +129,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
new String[] { account.name, account.type });
}
} catch(RemoteException e) {
App.log.log(Level.WARNING, "Couldn't re-assign contacts to new account name", e);
App.Companion.getLog().log(Level.WARNING, "Couldn't re-assign contacts to new account name", e);
}
}
}, null);
@ -180,7 +180,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
*/
public int verifyDirty() throws ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("verifyDirty() should not be called on Android <7");
App.Companion.getLog().severe("verifyDirty() should not be called on Android <7");
int reallyDirty = 0;
for (LocalContact contact : getDirtyContacts()) {
@ -189,10 +189,10 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
currentHash = contact.dataHashCode();
if (lastHash == currentHash) {
// hash is code still the same, contact is not "really dirty" (only metadata been have changed)
App.log.log(Level.FINE, "Contact data hash has not changed, resetting dirty flag", contact);
App.Companion.getLog().log(Level.FINE, "Contact data hash has not changed, resetting dirty flag", contact);
contact.resetDirty();
} else {
App.log.log(Level.FINE, "Contact data has changed from hash " + lastHash + " to " + currentHash, contact);
App.Companion.getLog().log(Level.FINE, "Contact data has changed from hash " + lastHash + " to " + currentHash, contact);
reallyDirty++;
}
} catch(FileNotFoundException e) {
@ -351,7 +351,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
/** should be done using {@link Groups.SUMMARY_COUNT}, but it's not implemented in Android yet */
for (LocalGroup group : (LocalGroup[])queryGroups(null, null))
if (group.getMembers().length == 0) {
App.log.log(Level.FINE, "Deleting group", group);
App.Companion.getLog().log(Level.FINE, "Deleting group", group);
group.delete();
}
}
@ -424,7 +424,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
try {
int fixed = provider.update(syncAdapterURI(RawContacts.CONTENT_URI),
values, where, null);
App.log.info("Fixed entries: " + String.valueOf(fixed));
App.Companion.getLog().info("Fixed entries: " + String.valueOf(fixed));
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't query contacts", e);
}

View File

@ -87,7 +87,7 @@ public class LocalCalendar extends AndroidCalendar implements LocalCollection {
if (ret.length == 1) {
return (LocalCalendar) ret[0];
} else {
App.log.severe("No calendar found for name " + name);
App.Companion.getLog().severe("No calendar found for name " + name);
return null;
}
}
@ -163,14 +163,14 @@ public class LocalCalendar extends AndroidCalendar implements LocalCollection {
@SuppressWarnings("Recycle")
public void processDirtyExceptions() throws CalendarStorageException {
// process deleted exceptions
App.log.info("Processing deleted exceptions");
App.Companion.getLog().info("Processing deleted exceptions");
try {
Cursor cursor = provider.query(
syncAdapterURI(Events.CONTENT_URI),
new String[] { Events._ID, Events.ORIGINAL_ID, LocalEvent.COLUMN_SEQUENCE },
Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NOT NULL", null, null);
while (cursor != null && cursor.moveToNext()) {
App.log.fine("Found deleted exception, removing; then re-schuling original event");
App.Companion.getLog().fine("Found deleted exception, removing; then re-schuling original event");
long id = cursor.getLong(0), // can't be null (by definition)
originalID = cursor.getLong(1); // can't be null (by query)
@ -201,14 +201,14 @@ public class LocalCalendar extends AndroidCalendar implements LocalCollection {
}
// process dirty exceptions
App.log.info("Processing dirty exceptions");
App.Companion.getLog().info("Processing dirty exceptions");
try {
Cursor cursor = provider.query(
syncAdapterURI(Events.CONTENT_URI),
new String[] { Events._ID, Events.ORIGINAL_ID, LocalEvent.COLUMN_SEQUENCE },
Events.DIRTY + "!=0 AND " + Events.ORIGINAL_ID + " IS NOT NULL", null, null);
while (cursor != null && cursor.moveToNext()) {
App.log.fine("Found dirty exception, increasing SEQUENCE to re-schedule");
App.Companion.getLog().fine("Found dirty exception, increasing SEQUENCE to re-schedule");
long id = cursor.getLong(0), // can't be null (by definition)
originalID = cursor.getLong(1); // can't be null (by query)
int sequence = cursor.isNull(2) ? 0 : cursor.getInt(2);
@ -279,7 +279,7 @@ public class LocalCalendar extends AndroidCalendar implements LocalCollection {
try {
int fixed = provider.update(syncAdapterURI(Events.CONTENT_URI),
values, where, whereArgs);
App.log.info("Fixed entries: " + String.valueOf(fixed));
App.Companion.getLog().info("Fixed entries: " + String.valueOf(fixed));
} catch (RemoteException e) {
throw new CalendarStorageException("Couldn't fix etags", e);
}

View File

@ -96,7 +96,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
int hashCode = dataHashCode();
values.put(COLUMN_HASHCODE, hashCode);
App.log.finer("Clearing dirty flag with eTag = " + eTag + ", contact hash = " + hashCode);
App.Companion.getLog().finer("Clearing dirty flag with eTag = " + eTag + ", contact hash = " + hashCode);
}
addressBook.provider.update(rawContactSyncURI(), values, null, null);
@ -128,7 +128,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
final Contact contact;
contact = getContact();
App.log.log(Level.FINE, "Preparing upload of VCard " + getUuid(), contact);
App.Companion.getLog().log(Level.FINE, "Preparing upload of VCard " + getUuid(), contact);
ByteArrayOutputStream os = new ByteArrayOutputStream();
contact.write(VCardVersion.V4_0, GROUP_VCARDS, os);
@ -194,7 +194,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
*/
protected int dataHashCode() throws FileNotFoundException, ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("dataHashCode() should not be called on Android <7");
App.Companion.getLog().severe("dataHashCode() should not be called on Android <7");
// reset contact so that getContact() reads from database
contact = null;
@ -202,18 +202,18 @@ public class LocalContact extends AndroidContact implements LocalResource {
// groupMemberships is filled by getContact()
int dataHash = getContact().hashCode(),
groupHash = groupMemberships.hashCode();
App.log.finest("Calculated data hash = " + dataHash + ", group memberships hash = " + groupHash);
App.Companion.getLog().finest("Calculated data hash = " + dataHash + ", group memberships hash = " + groupHash);
return dataHash ^ groupHash;
}
public void updateHashCode(@Nullable BatchOperation batch) throws ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("updateHashCode() should not be called on Android <7");
App.Companion.getLog().severe("updateHashCode() should not be called on Android <7");
ContentValues values = new ContentValues(1);
try {
int hashCode = dataHashCode();
App.log.fine("Storing contact hash = " + hashCode);
App.Companion.getLog().fine("Storing contact hash = " + hashCode);
values.put(COLUMN_HASHCODE, hashCode);
if (batch == null)
@ -231,7 +231,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
public int getLastHashCode() throws ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("getLastHashCode() should not be called on Android <7");
App.Companion.getLog().severe("getLastHashCode() should not be called on Android <7");
try {
Cursor c = addressBook.provider.query(rawContactSyncURI(), new String[] { COLUMN_HASHCODE }, null, null, null);

View File

@ -81,7 +81,7 @@ public class LocalEvent extends AndroidEvent implements LocalResource {
@Override
public String getContent() throws IOException, ContactsStorageException, CalendarStorageException {
App.log.log(Level.FINE, "Preparing upload of event " + getFileName(), getEvent());
App.Companion.getLog().log(Level.FINE, "Preparing upload of event " + getFileName(), getEvent());
ByteArrayOutputStream os = new ByteArrayOutputStream();
getEvent().write(os);

View File

@ -68,7 +68,7 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
final Contact contact;
contact = getContact();
App.log.log(Level.FINE, "Preparing upload of VCard " + getUuid(), contact);
App.Companion.getLog().log(Level.FINE, "Preparing upload of VCard " + getUuid(), contact);
ByteArrayOutputStream os = new ByteArrayOutputStream();
contact.write(VCardVersion.V4_0, GROUP_VCARDS, os);
@ -176,7 +176,7 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
BatchOperation batch = new BatchOperation(addressBook.provider);
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(0);
App.log.fine("Assigning members to group " + id);
App.Companion.getLog().fine("Assigning members to group " + id);
// required for workaround for Android 7 which sets DIRTY flag when only meta-data is changed
Set<Long> changeContactIDs = new HashSet<>();
@ -198,13 +198,13 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
// insert memberships
for (String uid : members) {
App.log.fine("Assigning member: " + uid);
App.Companion.getLog().fine("Assigning member: " + uid);
try {
LocalContact member = addressBook.findContactByUID(uid);
member.addToGroup(batch, id);
changeContactIDs.add(member.getId());
} catch(FileNotFoundException e) {
App.log.log(Level.WARNING, "Group member not found: " + uid, e);
App.Companion.getLog().log(Level.WARNING, "Group member not found: " + uid, e);
}
}

View File

@ -78,7 +78,7 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
contactsProvider.release()
val accountManager = AccountManager.get(context)
for (addressBookAccount in accountManager.getAccountsByType(App.getAddressBookAccountType())) {
for (addressBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) {
App.log.log(Level.INFO, "Running sync for address book", addressBookAccount)
val syncExtras = Bundle(extras)
syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true)

View File

@ -159,7 +159,7 @@ class AboutActivity : BaseActivity() {
companion object {
private val components = arrayOf(ComponentInfo(
App.getAppName(), BuildConfig.VERSION_NAME, Constants.webUri.toString(),
App.appName, 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"
), ComponentInfo(

View File

@ -281,11 +281,11 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
if (service == CollectionInfo.Type.ADDRESS_BOOK) {
info.carddav = AccountInfo.ServiceInfo()
info.carddav!!.id = id
info.carddav!!.refreshing = davService != null && davService!!.isRefreshing(id) || ContentResolver.isSyncActive(account, App.getAddressBooksAuthority())
info.carddav!!.refreshing = davService != null && davService!!.isRefreshing(id) || ContentResolver.isSyncActive(account, App.addressBooksAuthority)
info.carddav!!.journals = JournalEntity.getJournals(data, serviceEntity)
val accountManager = AccountManager.get(context)
for (addrBookAccount in accountManager.getAccountsByType(App.getAddressBookAccountType())) {
for (addrBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) {
val addressBook = LocalAddressBook(context, addrBookAccount, null)
try {
if (account == addressBook.mainAccount)
@ -396,7 +396,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
private val HINT_VIEW_COLLECTION = "ViewCollection"
protected fun requestSync(account: Account?) {
val authorities = arrayOf(App.getAddressBooksAuthority(), CalendarContract.AUTHORITY, TaskProvider.ProviderName.OpenTasks.authority)
val authorities = arrayOf(App.addressBooksAuthority, CalendarContract.AUTHORITY, TaskProvider.ProviderName.OpenTasks.authority)
for (authority in authorities) {
val extras = Bundle()

View File

@ -93,7 +93,7 @@ class AccountListFragment : ListFragment(), LoaderManager.LoaderCallbacks<Array<
@SuppressLint("MissingPermission")
override fun loadInBackground(): Array<Account>? {
return accountManager.getAccountsByType(App.getAccountType())
return accountManager.getAccountsByType(App.accountType)
}
}

View File

@ -97,7 +97,7 @@ class AccountSettingsActivity : BaseActivity() {
// category: synchronization
val prefSyncContacts = findPreference("sync_interval_contacts") as ListPreference
val syncIntervalContacts = settings.getSyncInterval(App.getAddressBooksAuthority())
val syncIntervalContacts = settings.getSyncInterval(App.addressBooksAuthority)
if (syncIntervalContacts != null) {
prefSyncContacts.value = syncIntervalContacts.toString()
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
@ -105,7 +105,7 @@ class AccountSettingsActivity : BaseActivity() {
else
prefSyncContacts.summary = getString(R.string.settings_sync_summary_periodically, prefSyncContacts.entry)
prefSyncContacts.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
settings.setSyncInterval(App.getAddressBooksAuthority(), java.lang.Long.parseLong(newValue as String))
settings.setSyncInterval(App.addressBooksAuthority, java.lang.Long.parseLong(newValue as String))
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false
}

View File

@ -155,7 +155,7 @@ class AppSettingsActivity : BaseActivity() {
}
private fun resetCertificates() {
(context!!.applicationContext as App).certManager.resetCertificates()
(context!!.applicationContext as App).certManager?.resetCertificates()
Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show()
}

View File

@ -20,15 +20,17 @@ open class BaseActivity : AppCompatActivity() {
super.onResume()
val app = applicationContext as App
if (app.certManager != null)
app.certManager.appInForeground = true
val certManager = app.certManager
if (certManager != null)
certManager.appInForeground = true
}
override fun onPause() {
super.onPause()
val app = applicationContext as App
if (app.certManager != null)
app.certManager.appInForeground = false
val certManager = app.certManager
if (certManager != null)
certManager.appInForeground = false
}
}

View File

@ -89,7 +89,7 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
// 1. find service ID
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
authority = App.getAddressBooksAuthority()
authority = App.addressBooksAuthority
} else if (info.type == CollectionInfo.Type.CALENDAR) {
authority = CalendarContract.AUTHORITY
} else {

View File

@ -168,7 +168,7 @@ class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String>
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
try {
val settings = AccountSettings(context, acct)
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.getAddressBooksAuthority())).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.syncWifiOnly)
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.addressBooksAuthority)).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.syncWifiOnly)
if (settings.syncWifiOnlySSID != null)
report.append(", SSID: ").append(settings.syncWifiOnlySSID)
report.append("\n [CardDAV] Contact group method: ").append(settings.groupMethod)
@ -179,7 +179,7 @@ class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String>
}
// address book accounts
for (acct in accountManager.getAccountsByType(App.getAddressBookAccountType()))
for (acct in accountManager.getAccountsByType(App.addressBookAccountType))
try {
val addressBook = LocalAddressBook(context, acct, null)
report.append("Address book account: ").append(acct.name).append("\n" + " Main account: ").append(addressBook.mainAccount).append("\n" + " URL: ").append(addressBook.url).append("\n" + " Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n")

View File

@ -20,8 +20,8 @@ internal class AccountResolver(private val context: Context) {
// Hardcoded swaps for known accounts:
if (accountName == "com.google") {
accountName = "com.google.android.googlequicksearchbox"
} else if (accountName == App.getAddressBookAccountType()) {
accountName = App.getAccountType()
} else if (accountName == App.addressBookAccountType) {
accountName = App.accountType
} else if (accountName == "at.bitfire.davdroid.address_book") {
accountName = "at.bitfire.davdroid"
}

View File

@ -69,7 +69,7 @@ class LoginCredentialsChangeFragment : DialogFragment(), LoaderManager.LoaderCal
return
}
settings.setAuthToken(data.authtoken!!)
settings.authToken = data.authtoken!!
}
} else
App.log.severe("Configuration detection failed")

View File

@ -94,7 +94,7 @@ class SetupEncryptionFragment : DialogFragment() {
try {
val cryptoManager: Crypto.CryptoManager
val httpClient = HttpClient.create(getContext(), config.url, config.authtoken)
val httpClient = HttpClient.create(getContext(), config.url, config.authtoken!!)
val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(config.url)!!)
val userInfo = userInfoManager[config.userName]
@ -116,7 +116,7 @@ class SetupEncryptionFragment : DialogFragment() {
@Throws(InvalidAccountException::class)
protected fun createAccount(accountName: String, config: BaseConfigurationFinder.Configuration): Boolean {
val account = Account(accountName, App.getAccountType())
val account = Account(accountName, App.accountType)
// create Android account
App.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, config.userName, config.url))
@ -132,7 +132,7 @@ class SetupEncryptionFragment : DialogFragment() {
try {
val settings = AccountSettings(context!!, account)
settings.authToken = config.authtoken
settings.authToken = config.authtoken!!
if (config.keyPair != null) {
settings.keyPair = config.keyPair!!
}
@ -142,9 +142,9 @@ class SetupEncryptionFragment : DialogFragment() {
insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV)
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL.toLong())
settings.setSyncInterval(App.addressBooksAuthority, Constants.DEFAULT_SYNC_INTERVAL.toLong())
} else {
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0)
ContentResolver.setIsSyncable(account, App.addressBooksAuthority, 0)
}
if (config.calDAV != null) {