mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-10-15 07:47:37 +00:00
Rewrite all remaining classes except syncadapter and ui package
This commit is contained in:
parent
53e333ee22
commit
e2c47bbe92
|
@ -1,484 +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 at.bitfire.davdroid;
|
||||
|
||||
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.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.HomeSets;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import at.bitfire.vcard4android.GroupMethod;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class AccountSettings {
|
||||
private final static int CURRENT_VERSION = 6;
|
||||
private final static String
|
||||
KEY_SETTINGS_VERSION = "version",
|
||||
|
||||
KEY_USERNAME = "user_name",
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle initialUserData(String userName) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
|
||||
bundle.putString(KEY_USERNAME, userName);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
||||
// authentication settings
|
||||
|
||||
public String username() { return accountManager.getUserData(account, KEY_USERNAME); }
|
||||
public void username(@NonNull String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
|
||||
|
||||
public String password() { return accountManager.getPassword(account); }
|
||||
public void password(@NonNull String password) { accountManager.setPassword(account, password); }
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
@Nullable
|
||||
public Integer getTimeRangePastDays() {
|
||||
String strDays = accountManager.getUserData(account, KEY_TIME_RANGE_PAST_DAYS);
|
||||
if (strDays != null) {
|
||||
int days = Integer.valueOf(strDays);
|
||||
return days < 0 ? null : days;
|
||||
} else
|
||||
return DEFAULT_TIME_RANGE_PAST_DAYS;
|
||||
}
|
||||
|
||||
public void setTimeRangePastDays(@Nullable Integer days) {
|
||||
accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, String.valueOf(days == null ? -1 : days));
|
||||
}
|
||||
|
||||
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) {
|
||||
for (int toVersion = fromVersion + 1; toVersion <= CURRENT_VERSION; toVersion++) {
|
||||
App.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion);
|
||||
try {
|
||||
Method updateProc = getClass().getDeclaredMethod("update_" + fromVersion + "_" + toVersion);
|
||||
updateProc.invoke(this);
|
||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, String.valueOf(toVersion));
|
||||
} catch (Exception e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't update account settings", e);
|
||||
}
|
||||
fromVersion = toVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_1_2() throws ContactsStorageException {
|
||||
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),
|
||||
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
|
||||
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState)
|
||||
- KEY_LAST_ANDROID_VERSION ("last_android_version") has been added
|
||||
*/
|
||||
|
||||
// move previous address book info to ContactsContract.SyncState
|
||||
@Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
||||
if (provider == null)
|
||||
throw new ContactsStorageException("Couldn't access Contacts provider");
|
||||
|
||||
LocalAddressBook addr = new LocalAddressBook(context, account, provider);
|
||||
|
||||
// until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
|
||||
addr.updateSettings(values);
|
||||
|
||||
String url = accountManager.getUserData(account, "addressbook_url");
|
||||
if (!TextUtils.isEmpty(url))
|
||||
addr.setURL(url);
|
||||
accountManager.setUserData(account, "addressbook_url", null);
|
||||
|
||||
String cTag = accountManager.getUserData(account, "addressbook_ctag");
|
||||
if (!TextUtils.isEmpty(cTag))
|
||||
addr.setCTag(cTag);
|
||||
accountManager.setUserData(account, "addressbook_ctag", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_2_3() {
|
||||
// Don't show a warning for Android updates anymore
|
||||
accountManager.setUserData(account, "last_android_version", null);
|
||||
|
||||
Long serviceCardDAV = null, serviceCalDAV = null;
|
||||
|
||||
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
|
||||
try {
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
// we have to create the WebDAV Service database only from the old address book, calendar and task list URLs
|
||||
|
||||
// CardDAV: migrate address books
|
||||
ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
||||
if (client != null)
|
||||
try {
|
||||
LocalAddressBook addrBook = new LocalAddressBook(context, account, client);
|
||||
String url = addrBook.getURL();
|
||||
if (url != null) {
|
||||
App.log.fine("Migrating address book " + url);
|
||||
|
||||
// insert CardDAV service
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Services.ACCOUNT_NAME, account.name);
|
||||
values.put(Services.SERVICE, Services.SERVICE_CARDDAV);
|
||||
serviceCardDAV = db.insert(Services._TABLE, null, values);
|
||||
|
||||
// insert address book
|
||||
values.clear();
|
||||
values.put(Collections.SERVICE_ID, serviceCardDAV);
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.SYNC, 1);
|
||||
db.insert(Collections._TABLE, null, values);
|
||||
|
||||
// insert home set
|
||||
HttpUrl homeSet = HttpUrl.parse(url).resolve("../");
|
||||
values.clear();
|
||||
values.put(HomeSets.SERVICE_ID, serviceCardDAV);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insert(HomeSets._TABLE, null, values);
|
||||
}
|
||||
|
||||
} catch (ContactsStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate address book", e);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
// CalDAV: migrate calendars + task lists
|
||||
Set<String> collections = new HashSet<>();
|
||||
Set<HttpUrl> homeSets = new HashSet<>();
|
||||
|
||||
client = context.getContentResolver().acquireContentProviderClient(CalendarContract.AUTHORITY);
|
||||
if (client != null)
|
||||
try {
|
||||
List<LocalCalendar> calendars = LocalCalendar.find(account, client, LocalCalendar.Factory.INSTANCE, null, null);
|
||||
for (LocalCalendar calendar : calendars) {
|
||||
String url = calendar.getName();
|
||||
App.log.fine("Migrating calendar " + url);
|
||||
collections.add(url);
|
||||
homeSets.add(HttpUrl.parse(url).resolve("../"));
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate calendars", e);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
TaskProvider provider = LocalTaskList.acquireTaskProvider(context.getContentResolver());
|
||||
if (provider != null)
|
||||
try {
|
||||
List<LocalTaskList> taskLists = LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
|
||||
for (LocalTaskList taskList : taskLists) {
|
||||
String url = taskList.getSyncId();
|
||||
App.log.fine("Migrating task list " + url);
|
||||
collections.add(url);
|
||||
homeSets.add(HttpUrl.parse(url).resolve("../"));
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate task lists", e);
|
||||
} finally {
|
||||
provider.close();
|
||||
}
|
||||
|
||||
if (!collections.isEmpty()) {
|
||||
// insert CalDAV service
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Services.ACCOUNT_NAME, account.name);
|
||||
values.put(Services.SERVICE, Services.SERVICE_CALDAV);
|
||||
serviceCalDAV = db.insert(Services._TABLE, null, values);
|
||||
|
||||
// insert collections
|
||||
for (String url : collections) {
|
||||
values.clear();
|
||||
values.put(Collections.SERVICE_ID, serviceCalDAV);
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.SYNC, 1);
|
||||
db.insert(Collections._TABLE, null, values);
|
||||
}
|
||||
|
||||
// insert home sets
|
||||
for (HttpUrl homeSet : homeSets) {
|
||||
values.clear();
|
||||
values.put(HomeSets.SERVICE_ID, serviceCalDAV);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insert(HomeSets._TABLE, null, values);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
// initiate service detection (refresh) to get display names, colors etc.
|
||||
Intent refresh = new Intent(context, DavService.class);
|
||||
refresh.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
if (serviceCardDAV != null) {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceCardDAV);
|
||||
context.startService(refresh);
|
||||
}
|
||||
if (serviceCalDAV != null) {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceCalDAV);
|
||||
context.startService(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_3_4() {
|
||||
setGroupMethod(GroupMethod.CATEGORIES);
|
||||
}
|
||||
|
||||
/* Android 7.1.1 OpenTasks fix */
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_4_5() {
|
||||
// call PackageChangedReceiver which then enables/disables OpenTasks sync when it's (not) available
|
||||
PackageChangedReceiver.updateTaskSync(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_5_6() throws ContactsStorageException {
|
||||
@Cleanup("release") 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)
|
||||
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||
byte[] raw = ContactsContract.SyncState.get(provider, account);
|
||||
if (raw == null)
|
||||
App.log.info("No contacts sync state, ignoring account");
|
||||
else {
|
||||
parcel.unmarshall(raw, 0, raw.length);
|
||||
parcel.setDataPosition(0);
|
||||
Bundle params = parcel.readBundle();
|
||||
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(url);
|
||||
info.setType(CollectionInfo.Type.ADDRESS_BOOK);
|
||||
info.setDisplayName(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, LocalAddressBook.initialUserData(account, info.getUrl())))
|
||||
throw new ContactsStorageException("Couldn't create address book account");
|
||||
LocalAddressBook addressBook = 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);
|
||||
int 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");
|
||||
}
|
||||
|
||||
ContactsContract.SyncState.set(provider, account, null);
|
||||
}
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't migrate contacts to new address book", e);
|
||||
}
|
||||
|
||||
// update version number so that further syncs don't repeat the migration
|
||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, "6");
|
||||
|
||||
// request sync of new address book account
|
||||
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 1);
|
||||
setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL);
|
||||
}
|
||||
|
||||
|
||||
public static class AppUpdatedReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
App.log.info("DAVdroid was updated, checking for AccountSettings version");
|
||||
|
||||
// peek into AccountSettings to initiate a possible migration
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
for (Account account : accountManager.getAccountsByType(context.getString(R.string.account_type)))
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
452
app/src/main/java/at/bitfire/davdroid/AccountSettings.kt
Normal file
452
app/src/main/java/at/bitfire/davdroid/AccountSettings.kt
Normal file
|
@ -0,0 +1,452 @@
|
|||
/*
|
||||
* 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 at.bitfire.davdroid
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.RemoteException
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.ContactsContract
|
||||
import at.bitfire.davdroid.model.CollectionInfo
|
||||
import at.bitfire.davdroid.model.ServiceDB
|
||||
import at.bitfire.davdroid.model.ServiceDB.*
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.resource.LocalCalendar
|
||||
import at.bitfire.davdroid.resource.LocalTaskList
|
||||
import at.bitfire.ical4android.AndroidCalendar
|
||||
import at.bitfire.ical4android.AndroidTaskList
|
||||
import at.bitfire.ical4android.CalendarStorageException
|
||||
import at.bitfire.vcard4android.ContactsStorageException
|
||||
import at.bitfire.vcard4android.GroupMethod
|
||||
import okhttp3.HttpUrl
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
|
||||
class AccountSettings @Throws(InvalidAccountException::class) constructor(
|
||||
val context: Context,
|
||||
val account: Account
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
val CURRENT_VERSION = 6;
|
||||
val KEY_SETTINGS_VERSION = "version"
|
||||
|
||||
val KEY_USERNAME = "user_name"
|
||||
|
||||
val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false)
|
||||
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
|
||||
*/
|
||||
val KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days"
|
||||
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 */
|
||||
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
|
||||
*/
|
||||
val KEY_CONTACT_GROUP_METHOD = "contact_group_method"
|
||||
|
||||
@JvmField
|
||||
val SYNC_INTERVAL_MANUALLY = -1L
|
||||
|
||||
@JvmStatic
|
||||
fun initialUserData(userName: String): Bundle
|
||||
{
|
||||
val bundle = Bundle(2)
|
||||
bundle.putString(KEY_SETTINGS_VERSION, CURRENT_VERSION.toString())
|
||||
bundle.putString(KEY_USERNAME, userName)
|
||||
return bundle
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val accountManager: AccountManager = AccountManager.get(context)
|
||||
|
||||
init {
|
||||
synchronized(AccountSettings::class.java) {
|
||||
val versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION) ?: throw InvalidAccountException(account)
|
||||
var version = 0
|
||||
try {
|
||||
version = Integer.parseInt(versionStr)
|
||||
} catch (e: NumberFormatException) {
|
||||
}
|
||||
App.log.fine("Account ${account.name} has version $version, current version: $CURRENT_VERSION")
|
||||
|
||||
if (version < CURRENT_VERSION)
|
||||
update(version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// authentication settings
|
||||
|
||||
fun username(): String? = accountManager.getUserData(account, KEY_USERNAME)
|
||||
fun username(userName: String) = accountManager.setUserData(account, KEY_USERNAME, userName)
|
||||
|
||||
fun password(): String? = 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
|
||||
|
||||
return if (ContentResolver.getSyncAutomatically(account, authority))
|
||||
ContentResolver.getPeriodicSyncs(account, authority).firstOrNull()?.period ?: SYNC_INTERVAL_MANUALLY
|
||||
else
|
||||
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 getSyncWifiOnly() = accountManager.getUserData(account, KEY_WIFI_ONLY) != null
|
||||
fun setSyncWiFiOnly(wiFiOnly: Boolean) =
|
||||
accountManager.setUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null)
|
||||
|
||||
fun getSyncWifiOnlySSID(): String? = accountManager.getUserData(account, KEY_WIFI_ONLY_SSID)
|
||||
fun setSyncWifiOnlySSID(ssid: String) = accountManager.setUserData(account, KEY_WIFI_ONLY_SSID, ssid)
|
||||
|
||||
|
||||
// CalDAV settings
|
||||
|
||||
fun getTimeRangePastDays(): Int? {
|
||||
val strDays = accountManager.getUserData(account, KEY_TIME_RANGE_PAST_DAYS)
|
||||
if (strDays != null) {
|
||||
val days = Integer.valueOf(strDays)
|
||||
return if (days < 0) null else days
|
||||
} else
|
||||
return DEFAULT_TIME_RANGE_PAST_DAYS
|
||||
}
|
||||
|
||||
fun setTimeRangePastDays(days: Int?) =
|
||||
accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, (days ?: -1).toString())
|
||||
|
||||
fun getManageCalendarColors() = accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null
|
||||
fun setManageCalendarColors(manage: Boolean) =
|
||||
accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0")
|
||||
|
||||
|
||||
// CardDAV settings
|
||||
|
||||
fun getGroupMethod(): GroupMethod {
|
||||
val name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD)
|
||||
return if (name != null)
|
||||
GroupMethod.valueOf(name)
|
||||
else
|
||||
GroupMethod.GROUP_VCARDS
|
||||
}
|
||||
|
||||
fun setGroupMethod(method: GroupMethod) {
|
||||
val name = if (method == GroupMethod.GROUP_VCARDS) null else method.name
|
||||
accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name)
|
||||
}
|
||||
|
||||
|
||||
// update from previous account settings
|
||||
|
||||
private fun update(baseVersion: Int) {
|
||||
for (toVersion in baseVersion+1 .. CURRENT_VERSION) {
|
||||
val fromVersion = toVersion-1
|
||||
App.log.info("Updating account ${account.name} from version $fromVersion to $toVersion")
|
||||
try {
|
||||
val updateProc = this::class.java.getDeclaredMethod("update_${fromVersion}_$toVersion")
|
||||
updateProc.invoke(this)
|
||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, toVersion.toString())
|
||||
} catch (e: Exception) {
|
||||
App.log.log(Level.SEVERE, "Couldn't update account settings", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private fun update_1_2() {
|
||||
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),
|
||||
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
|
||||
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState)
|
||||
- KEY_LAST_ANDROID_VERSION ("last_android_version") has been added
|
||||
*/
|
||||
|
||||
// move previous address book info to ContactsContract.SyncState
|
||||
val provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY) ?:
|
||||
throw ContactsStorageException("Couldn't access Contacts provider")
|
||||
|
||||
try {
|
||||
val addr = LocalAddressBook(context, account, provider)
|
||||
|
||||
// until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
|
||||
val values = ContentValues()
|
||||
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1)
|
||||
addr.updateSettings(values)
|
||||
|
||||
val url = accountManager.getUserData(account, "addressbook_url")
|
||||
if (!url.isNullOrEmpty())
|
||||
addr.setURL(url)
|
||||
accountManager.setUserData(account, "addressbook_url", null)
|
||||
|
||||
val cTag = accountManager.getUserData (account, "addressbook_ctag")
|
||||
if (!cTag.isNullOrEmpty())
|
||||
addr.setCTag(cTag)
|
||||
accountManager.setUserData(account, "addressbook_ctag", null)
|
||||
} finally {
|
||||
if (Build.VERSION.SDK_INT >= 24)
|
||||
provider.close()
|
||||
else
|
||||
provider.release()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private fun update_2_3() {
|
||||
// Don't show a warning for Android updates anymore
|
||||
accountManager.setUserData(account, "last_android_version", null)
|
||||
|
||||
var serviceCardDAV: Long? = null
|
||||
var serviceCalDAV: Long? = null
|
||||
|
||||
ServiceDB.OpenHelper(context).use { dbHelper ->
|
||||
val db = dbHelper.writableDatabase
|
||||
// we have to create the WebDAV Service database only from the old address book, calendar and task list URLs
|
||||
|
||||
// CardDAV: migrate address books
|
||||
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.let { client ->
|
||||
try {
|
||||
val addrBook = LocalAddressBook(context, account, client)
|
||||
val url = addrBook.getURL()
|
||||
App.log.fine("Migrating address book $url")
|
||||
|
||||
// insert CardDAV service
|
||||
val values = ContentValues(3)
|
||||
values.put(Services.ACCOUNT_NAME, account.name)
|
||||
values.put(Services.SERVICE, Services.SERVICE_CARDDAV)
|
||||
serviceCardDAV = db.insert(Services._TABLE, null, values)
|
||||
|
||||
// insert address book
|
||||
values.clear()
|
||||
values.put(Collections.SERVICE_ID, serviceCardDAV)
|
||||
values.put(Collections.URL, url)
|
||||
values.put(Collections.SYNC, 1)
|
||||
db.insert(Collections._TABLE, null, values)
|
||||
|
||||
// insert home set
|
||||
HttpUrl.parse(url)?.let {
|
||||
val homeSet = it.resolve("../")
|
||||
values.clear()
|
||||
values.put(HomeSets.SERVICE_ID, serviceCardDAV)
|
||||
values.put(HomeSets.URL, homeSet.toString())
|
||||
db.insert(HomeSets._TABLE, null, values)
|
||||
}
|
||||
} catch (e: ContactsStorageException) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate address book", e)
|
||||
} finally {
|
||||
if (Build.VERSION.SDK_INT >= 24)
|
||||
client.close()
|
||||
else
|
||||
client.release()
|
||||
}
|
||||
}
|
||||
|
||||
// CalDAV: migrate calendars + task lists
|
||||
val collections = HashSet<String>()
|
||||
val homeSets = HashSet<HttpUrl>()
|
||||
|
||||
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.let { client ->
|
||||
try {
|
||||
val calendars = AndroidCalendar.find(account, client, LocalCalendar.Factory, null, null)
|
||||
for (calendar in calendars)
|
||||
calendar.name?.let { url ->
|
||||
App.log.fine("Migrating calendar $url")
|
||||
collections.add(url)
|
||||
HttpUrl.parse(url)?.resolve("../")?.let { homeSets.add(it) }
|
||||
}
|
||||
} catch (e: CalendarStorageException) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate calendars", e)
|
||||
} finally {
|
||||
if (Build.VERSION.SDK_INT >= 24)
|
||||
client.close()
|
||||
else
|
||||
client.release()
|
||||
}
|
||||
}
|
||||
|
||||
AndroidTaskList.acquireTaskProvider(context.contentResolver)?.use { provider ->
|
||||
try {
|
||||
val taskLists = AndroidTaskList.find(account, provider, LocalTaskList.Factory, null, null)
|
||||
for (taskList in taskLists)
|
||||
taskList.syncId?.let { url ->
|
||||
App.log.fine("Migrating task list $url")
|
||||
collections.add(url)
|
||||
HttpUrl.parse(url)?.resolve("../")?.let { homeSets.add(it) }
|
||||
}
|
||||
} catch (e: CalendarStorageException) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate task lists", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!collections.isEmpty()) {
|
||||
// insert CalDAV service
|
||||
val values = ContentValues(3)
|
||||
values.put(Services.ACCOUNT_NAME, account.name)
|
||||
values.put(Services.SERVICE, Services.SERVICE_CALDAV)
|
||||
serviceCalDAV = db.insert(Services._TABLE, null, values)
|
||||
|
||||
// insert collections
|
||||
for (url in collections) {
|
||||
values.clear()
|
||||
values.put(Collections.SERVICE_ID, serviceCalDAV)
|
||||
values.put(Collections.URL, url)
|
||||
values.put(Collections.SYNC, 1)
|
||||
db.insert(Collections._TABLE, null, values)
|
||||
}
|
||||
|
||||
// insert home sets
|
||||
for (homeSet in homeSets) {
|
||||
values.clear()
|
||||
values.put(HomeSets.SERVICE_ID, serviceCalDAV)
|
||||
values.put(HomeSets.URL, homeSet.toString())
|
||||
db.insert(HomeSets._TABLE, null, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initiate service detection (refresh) to get display names, colors etc.
|
||||
val refresh = Intent(context, DavService::class.java)
|
||||
refresh.action = DavService.ACTION_REFRESH_COLLECTIONS
|
||||
serviceCardDAV?.let {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, it)
|
||||
context.startService(refresh)
|
||||
}
|
||||
serviceCalDAV?.let {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, it)
|
||||
context.startService(refresh)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private fun update_3_4() {
|
||||
setGroupMethod(GroupMethod.CATEGORIES)
|
||||
}
|
||||
|
||||
/* Android 7.1.1 OpenTasks fix */
|
||||
@SuppressWarnings("unused")
|
||||
private fun update_4_5() {
|
||||
// call PackageChangedReceiver which then enables/disables OpenTasks sync when it's (not) available
|
||||
PackageChangedReceiver.updateTaskSync(context)
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private fun update_5_6() {
|
||||
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { provider ->
|
||||
// don't run syncs during the migration
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0)
|
||||
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0)
|
||||
ContentResolver.cancelSync(account, null)
|
||||
|
||||
val parcel = Parcel.obtain()
|
||||
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 {
|
||||
parcel.unmarshall(raw, 0, raw.size)
|
||||
parcel.setDataPosition(0)
|
||||
val params = parcel.readBundle()
|
||||
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(url)
|
||||
info.type = CollectionInfo.Type.ADDRESS_BOOK
|
||||
info.displayName = account.name
|
||||
App.log.log(Level.INFO, "Creating new address book account", url)
|
||||
val addressBookAccount = Account(LocalAddressBook.accountName(account, info), App.getAddressBookAccountType())
|
||||
if (!accountManager.addAccountExplicitly(addressBookAccount, null, LocalAddressBook.initialUserData(account, info.url)))
|
||||
throw ContactsStorageException("Couldn't create address book account")
|
||||
val addressBook = 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)
|
||||
val 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))
|
||||
App.log.info("$affected contacts moved to new address book")
|
||||
}
|
||||
|
||||
ContactsContract.SyncState.set(provider, account, null)
|
||||
}
|
||||
} catch(e: RemoteException) {
|
||||
throw ContactsStorageException("Couldn't migrate contacts to new address book", e)
|
||||
} finally {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
// update version number so that further syncs don't repeat the migration
|
||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, "6")
|
||||
|
||||
// request sync of new address book account
|
||||
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 1)
|
||||
setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL)
|
||||
}
|
||||
|
||||
|
||||
class AppUpdatedReceiver: BroadcastReceiver() {
|
||||
|
||||
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
App.log.info("DAVdroid was updated, checking for AccountSettings version")
|
||||
|
||||
// peek into AccountSettings to initiate a possible migration
|
||||
val accountManager = AccountManager.get(context)
|
||||
for (account in accountManager.getAccountsByType(context.getString(R.string.account_type)))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
package at.bitfire.davdroid;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
@ -17,7 +16,6 @@ import android.content.Intent;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -42,7 +40,6 @@ import at.bitfire.davdroid.log.PlainTextFormatter;
|
|||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.Settings;
|
||||
import lombok.Cleanup;
|
||||
import lombok.Getter;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
|
||||
public class App extends Application {
|
||||
|
@ -56,14 +53,14 @@ public class App extends Application {
|
|||
public static final String OVERRIDE_PROXY_HOST_DEFAULT = "localhost";
|
||||
public static final int OVERRIDE_PROXY_PORT_DEFAULT = 8118;
|
||||
|
||||
@Getter
|
||||
private CustomCertManager certManager;
|
||||
public CustomCertManager getCertManager() { return certManager; }
|
||||
|
||||
@Getter
|
||||
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
|
||||
public static SSLSocketFactoryCompat getSslSocketFactoryCompat() { return sslSocketFactoryCompat; }
|
||||
|
||||
@Getter
|
||||
private static HostnameVerifier hostnameVerifier;
|
||||
public static HostnameVerifier getHostnameVerifier() { return hostnameVerifier; }
|
||||
|
||||
public final static Logger log = Logger.getLogger("davdroid");
|
||||
static {
|
||||
|
@ -74,8 +71,8 @@ public class App extends Application {
|
|||
private static String addressBookAccountType;
|
||||
public static String getAddressBookAccountType() { return addressBookAccountType; }
|
||||
|
||||
@Getter
|
||||
private static String addressBooksAuthority;
|
||||
public static String getAddressBooksAuthority() { return addressBooksAuthority; }
|
||||
|
||||
@Override
|
||||
@SuppressLint("HardwareIds")
|
||||
|
|
|
@ -1,25 +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 at.bitfire.davdroid;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public class Constants {
|
||||
|
||||
// notification IDs
|
||||
public final static int
|
||||
NOTIFICATION_EXTERNAL_FILE_LOGGING = 1,
|
||||
NOTIFICATION_REFRESH_COLLECTIONS = 2,
|
||||
NOTIFICATION_CONTACTS_SYNC = 10,
|
||||
NOTIFICATION_CALENDAR_SYNC = 11,
|
||||
NOTIFICATION_TASK_SYNC = 12,
|
||||
NOTIFICATION_PERMISSIONS = 20;
|
||||
|
||||
public static final int DEFAULT_SYNC_INTERVAL = 4 * 3600; // 4 hours
|
||||
|
||||
}
|
23
app/src/main/java/at/bitfire/davdroid/Constants.kt
Normal file
23
app/src/main/java/at/bitfire/davdroid/Constants.kt
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 at.bitfire.davdroid;
|
||||
|
||||
object Constants {
|
||||
|
||||
// notification IDs
|
||||
@JvmField val NOTIFICATION_EXTERNAL_FILE_LOGGING = 1
|
||||
@JvmField val NOTIFICATION_REFRESH_COLLECTIONS = 2
|
||||
@JvmField val NOTIFICATION_CONTACTS_SYNC = 10
|
||||
@JvmField val NOTIFICATION_CALENDAR_SYNC = 11
|
||||
@JvmField val NOTIFICATION_TASK_SYNC = 12
|
||||
@JvmField val NOTIFICATION_PERMISSIONS = 20
|
||||
|
||||
@JvmField
|
||||
val DEFAULT_SYNC_INTERVAL = 4 * 3600L // 4 hours
|
||||
|
||||
}
|
|
@ -1,176 +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 at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
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 at.bitfire.dav4android.BasicDigestAuthHandler;
|
||||
import at.bitfire.dav4android.UrlUtils;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.Settings;
|
||||
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 = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android; okhttp3) Android/" + Build.VERSION.RELEASE;
|
||||
}
|
||||
|
||||
private HttpClient() {
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@Nullable Context context, @NonNull AccountSettings settings, @NonNull final Logger logger) {
|
||||
OkHttpClient.Builder builder = defaultBuilder(context, logger);
|
||||
|
||||
// use account settings for authentication
|
||||
builder = addAuthentication(builder, null, settings.username(), settings.password());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
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(@NonNull Context context, @NonNull Account account) throws InvalidAccountException {
|
||||
AccountSettings settings = new AccountSettings(context, account);
|
||||
return create(context, settings, App.log);
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@Nullable Context context) {
|
||||
return create(context, App.log);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// don't allow redirects, because it would break PROPFIND handling
|
||||
builder.followRedirects(false);
|
||||
|
||||
// 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 cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
||||
builder.cookieJar(new MemoryCookieStore());
|
||||
|
||||
// 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, String username, String password) {
|
||||
if (username != null && password != null) {
|
||||
BasicDigestAuthHandler authHandler = new BasicDigestAuthHandler(UrlUtils.hostToDomain(host), username, password);
|
||||
return builder
|
||||
.addNetworkInterceptor(authHandler)
|
||||
.authenticator(authHandler);
|
||||
} else
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
addAuthentication(builder, null, username, password);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
addAuthentication(builder, host, username, password);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
153
app/src/main/java/at/bitfire/davdroid/HttpClient.kt
Normal file
153
app/src/main/java/at/bitfire/davdroid/HttpClient.kt
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import at.bitfire.dav4android.BasicDigestAuthHandler
|
||||
import at.bitfire.dav4android.UrlUtils
|
||||
import at.bitfire.davdroid.model.ServiceDB
|
||||
import at.bitfire.davdroid.model.Settings
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
|
||||
class HttpClient private constructor() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val client = OkHttpClient()
|
||||
private val userAgentInterceptor = UserAgentInterceptor()
|
||||
|
||||
private val userAgentDate = SimpleDateFormat("yyyy/MM/dd", Locale.US).format(Date(BuildConfig.buildTime))
|
||||
private val userAgent = "DAVdroid/${BuildConfig.VERSION_NAME} ($userAgentDate; dav4android; okhttp3) Android/${Build.VERSION.RELEASE}"
|
||||
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun create(context: Context?, settings: AccountSettings? = null, logger: Logger = App.log): OkHttpClient {
|
||||
var builder = defaultBuilder(context, logger)
|
||||
|
||||
// use account settings for authentication
|
||||
settings?.let {
|
||||
val userName = it.username()
|
||||
val password = it.password()
|
||||
if (userName != null && password != null)
|
||||
builder = addAuthentication(builder, null, userName, password)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(InvalidAccountException::class)
|
||||
fun create(context: Context, account: Account) =
|
||||
create(context, AccountSettings(context, account))
|
||||
|
||||
|
||||
private fun defaultBuilder(context: Context?, logger: Logger): OkHttpClient.Builder {
|
||||
val builder = client.newBuilder()
|
||||
|
||||
// use MemorizingTrustManager to manage self-signed certificates
|
||||
context?.let {
|
||||
val app = it.applicationContext as App
|
||||
if (App.getSslSocketFactoryCompat() != null && app.certManager != null)
|
||||
builder.sslSocketFactory(App.getSslSocketFactoryCompat(), app.certManager)
|
||||
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)
|
||||
|
||||
// don't allow redirects, because it would break PROPFIND handling
|
||||
builder.followRedirects(false)
|
||||
|
||||
// custom proxy support
|
||||
context?.let {
|
||||
ServiceDB.OpenHelper(it).use { dbHelper ->
|
||||
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: Exception) {
|
||||
App.log.log(Level.SEVERE, "Can't set proxy, ignoring", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add User-Agent to every request
|
||||
builder.addNetworkInterceptor(userAgentInterceptor)
|
||||
|
||||
// add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
||||
builder.cookieJar(MemoryCookieStore())
|
||||
|
||||
// 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?, username: String, password: String): OkHttpClient.Builder {
|
||||
val authHandler = BasicDigestAuthHandler(UrlUtils.hostToDomain(host), username, password);
|
||||
return builder
|
||||
.addNetworkInterceptor(authHandler)
|
||||
.authenticator(authHandler)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
fun addAuthentication(client: OkHttpClient, host: String? = null, username: String, password: String): OkHttpClient {
|
||||
val builder = client.newBuilder()
|
||||
addAuthentication(builder, host, username, password)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class UserAgentInterceptor: Interceptor {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -82,7 +82,7 @@ public class DavResourceFinder {
|
|||
log.setLevel(Level.FINEST);
|
||||
log.addHandler(logBuffer);
|
||||
|
||||
httpClient = HttpClient.create(context, log);
|
||||
httpClient = HttpClient.create(context, null, log);
|
||||
httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password);
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b8a3580dc055282a92555231c2e6d55a6931bc09
|
||||
Subproject commit 1d4b2348faf07a45b95440479e3832ae958df989
|
Loading…
Reference in a new issue