Refactor Settings provider

* don't use a separate :sync process anymore, so that settings management doesn't need IPC
* remove Settings service and IPC, use singleton with application Context instead
* adapt default number of sync worker threads
* library updates
This commit is contained in:
Ricki Hirner 2018-12-26 11:51:33 +01:00
parent b863d355f6
commit 7d4689969a
48 changed files with 663 additions and 916 deletions

185
Settings.kt Normal file
View file

@ -0,0 +1,185 @@
/*
* 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 at.bitfire.davdroid.settings
import android.content.Context
import at.bitfire.davdroid.log.Logger
import java.lang.ref.WeakReference
import java.util.*
import java.util.logging.Level
class Settings(
appContext: Context
) {
companion object {
// settings keys and default values
const val DISTRUST_SYSTEM_CERTIFICATES = "distrust_system_certs"
const val DISTRUST_SYSTEM_CERTIFICATES_DEFAULT = false
const val OVERRIDE_PROXY = "override_proxy"
const val OVERRIDE_PROXY_DEFAULT = false
const val OVERRIDE_PROXY_HOST = "override_proxy_host"
const val OVERRIDE_PROXY_PORT = "override_proxy_port"
const val OVERRIDE_PROXY_HOST_DEFAULT = "localhost"
const val OVERRIDE_PROXY_PORT_DEFAULT = 8118
private var singleton: Settings? = null
fun getInstance(context: Context): Settings {
singleton?.let { return it }
val newInstance = Settings(context.applicationContext)
singleton = newInstance
return newInstance
}
}
private val providers = LinkedList<SettingsProvider>()
private val observers = LinkedList<WeakReference<OnChangeListener>>()
init {
ServiceLoader.load(ISettingsProviderFactory::class.java).forEach { factory ->
providers.addAll(factory.getProviders(appContext))
}
}
fun forceReload() {
providers.forEach {
it.forceReload()
}
onSettingsChanged()
}
/*** OBSERVERS ***/
fun addOnChangeListener(observer: OnChangeListener) {
observers += WeakReference(observer)
}
fun removeOnChangeListener(observer: OnChangeListener) {
observers.removeAll { it.get() == null || it.get() == observer }
}
fun onSettingsChanged() {
observers.mapNotNull { it.get() }.forEach {
it.onSettingsChanged()
}
}
/*** SETTINGS ACCESS ***/
fun has(key: String): Boolean {
Logger.log.fine("Looking for setting $key")
var result = false
for (provider in providers)
try {
val (value, further) = provider.has(key)
Logger.log.finer("${provider::class.java.simpleName}: has $key = $value, continue: $further")
if (value) {
result = true
break
}
if (!further)
break
} catch(e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't look up setting in $provider", e)
}
Logger.log.fine("Looking for setting $key -> $result")
return result
}
private fun<T> getValue(key: String, reader: (SettingsProvider) -> Pair<T?, Boolean>): T? {
Logger.log.fine("Looking up setting $key")
var result: T? = null
for (provider in providers)
try {
val (value, further) = reader(provider)
Logger.log.finer("${provider::class.java.simpleName}: value = $value, continue: $further")
value?.let { result = it }
if (!further)
break
} catch(e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't read setting from $provider", e)
}
Logger.log.fine("Looked up setting $key -> $result")
return result
}
fun getBoolean(key: String) =
getValue(key) { provider -> provider.getBoolean(key) }
fun getInt(key: String) =
getValue(key) { provider -> provider.getInt(key) }
fun getLong(key: String) =
getValue(key) { provider -> provider.getLong(key) }
fun getString(key: String) =
getValue(key) { provider -> provider.getString(key) }
fun isWritable(key: String): Boolean {
for (provider in providers) {
val (value, further) = provider.isWritable(key)
if (value)
return true
if (!further)
return false
}
return false
}
private fun<T> putValue(key: String, value: T?, writer: (SettingsProvider) -> Boolean): Boolean {
Logger.log.fine("Trying to write setting $key = $value")
for (provider in providers) {
val (writable, further) = provider.isWritable(key)
Logger.log.finer("${provider::class.java.simpleName}: writable = $writable, continue: $further")
if (writable)
return try {
writer(provider)
} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't write setting to $provider", e)
false
}
if (!further)
return false
}
return false
}
fun putBoolean(key: String, value: Boolean?) =
putValue(key, value) { provider -> provider.putBoolean(key, value) }
fun putInt(key: String, value: Int?) =
putValue(key, value) { provider -> provider.putInt(key, value) }
fun putLong(key: String, value: Long?) =
putValue(key, value) { provider -> provider.putLong(key, value) }
fun putString(key: String, value: String?) =
putValue(key, value) { provider -> provider.putString(key, value) }
fun remove(key: String): Boolean {
var deleted = false
providers.forEach { deleted = deleted || it.remove(key) }
return deleted
}
interface OnChangeListener {
fun onSettingsChanged()
}
}

View file

@ -33,7 +33,6 @@ android {
productFlavors {
standard {
versionName "2.0.7-ose"
buildConfigField "boolean", "customCerts", "true"
}
}
@ -83,7 +82,7 @@ dependencies {
implementation 'com.github.yukuku:ambilwarna:2.0.1'
implementation 'com.mikepenz:aboutlibraries:6.2.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
implementation 'commons-io:commons-io:2.6'
implementation 'dnsjava:dnsjava:2.1.8'
implementation 'org.apache.commons:commons-lang3:3.8.1'
@ -93,8 +92,8 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.0'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1'
testImplementation 'junit:junit:4.12'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1'
}

View file

@ -8,32 +8,31 @@
package at.bitfire.davdroid.settings
import at.bitfire.davdroid.App
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test
class DefaultsProviderTest {
class DefaultsSettingsProviderTest {
private val provider: Provider = DefaultsProvider()
private val provider: SettingsProvider = DefaultsProvider()
@Test
fun testHas() {
assertEquals(Pair(false, true), provider.has("notExisting"))
assertEquals(Pair(true, true), provider.has(App.OVERRIDE_PROXY))
assertEquals(Pair(true, true), provider.has(Settings.OVERRIDE_PROXY))
}
@Test
fun testGet() {
assertEquals(Pair("localhost", true), provider.getString(App.OVERRIDE_PROXY_HOST))
assertEquals(Pair(8118, true), provider.getInt(App.OVERRIDE_PROXY_PORT))
assertEquals(Pair("localhost", true), provider.getString(Settings.OVERRIDE_PROXY_HOST))
assertEquals(Pair(8118, true), provider.getInt(Settings.OVERRIDE_PROXY_PORT))
}
@Test
fun testPutRemove() {
assertEquals(Pair(false, true), provider.isWritable(App.OVERRIDE_PROXY))
assertFalse(provider.putBoolean(App.OVERRIDE_PROXY, true))
assertFalse(provider.remove(App.OVERRIDE_PROXY))
assertEquals(Pair(false, true), provider.isWritable(Settings.OVERRIDE_PROXY))
assertFalse(provider.putBoolean(Settings.OVERRIDE_PROXY, true))
assertFalse(provider.remove(Settings.OVERRIDE_PROXY))
}
}

View file

@ -9,8 +9,6 @@
package at.bitfire.davdroid.settings
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.davdroid.App
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@ -18,25 +16,19 @@ import org.junit.Test
class SettingsTest {
lateinit var settings: Settings.Stub
lateinit var settings: Settings
@Before
fun init() {
settings = Settings.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)!!
fun initialize() {
settings = Settings.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
}
@After
fun shutdown() {
settings.close()
}
@Test
fun testHas() {
assertFalse(settings.has("notExisting"))
// provided by DefaultsProvider
assertTrue(settings.has(App.OVERRIDE_PROXY))
assertTrue(settings.has(Settings.OVERRIDE_PROXY))
}
}

View file

@ -57,7 +57,6 @@
tools:ignore="UnusedAttribute">
<service android:name=".DavService"/>
<service android:name=".settings.Settings"/>
<activity
android:name=".ui.AccountsActivity"
@ -130,7 +129,6 @@
<service
android:name=".syncadapter.CalendarsSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
@ -143,7 +141,6 @@
<service
android:name=".syncadapter.TasksSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
@ -175,7 +172,6 @@
<service
android:name=".syncadapter.AddressBooksSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
@ -188,7 +184,6 @@
<service
android:name=".syncadapter.ContactsSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>

View file

@ -1,28 +0,0 @@
package at.bitfire.davdroid.settings;
import at.bitfire.davdroid.settings.ISettingsObserver;
interface ISettings {
void forceReload();
boolean has(String key);
boolean getBoolean(String key, boolean defaultValue);
int getInt(String key, int defaultValue);
long getLong(String key, long defaultValue);
String getString(String key, String defaultValue);
boolean isWritable(String key);
boolean putBoolean(String key, boolean value);
boolean putInt(String key, int value);
boolean putLong(String key, long value);
boolean putString(String key, String value);
boolean remove(String key);
void registerObserver(ISettingsObserver observer);
void unregisterObserver(ISettingsObserver observer);
}

View file

@ -1,7 +0,0 @@
package at.bitfire.davdroid.settings;
interface ISettingsObserver {
void onSettingsChanged();
}

View file

@ -27,15 +27,6 @@ class App: Application() {
companion object {
const val DISTRUST_SYSTEM_CERTIFICATES = "distrust_system_certs"
const val OVERRIDE_PROXY = "override_proxy"
const val OVERRIDE_PROXY_HOST = "override_proxy_host"
const val OVERRIDE_PROXY_PORT = "override_proxy_port"
const val OVERRIDE_PROXY_HOST_DEFAULT = "localhost"
const val OVERRIDE_PROXY_PORT_DEFAULT = 8118
fun getLauncherBitmap(context: Context): Bitmap? {
val drawableLogo = if (android.os.Build.VERSION.SDK_INT >= 21)
context.getDrawable(R.mipmap.ic_launcher)
@ -62,7 +53,7 @@ class App: Application() {
super.onCreate()
Logger.initialize(this)
if (BuildConfig.DEBUG) {
if (BuildConfig.DEBUG)
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectFileUriExposure()
@ -72,13 +63,6 @@ class App: Application() {
.penaltyLog()
.build())
// main thread
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build())
}
if (Build.VERSION.SDK_INT <= 21)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)

View file

@ -30,7 +30,7 @@ import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.ServiceDB.*
import at.bitfire.davdroid.model.ServiceDB.Collections
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.DebugInfoActivity
import at.bitfire.davdroid.ui.NotificationUtils
import okhttp3.HttpUrl
@ -293,84 +293,81 @@ class DavService: Service() {
NotificationManagerCompat.from(this)
.cancel(service.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS)
Settings.getInstance(this)?.use { settings ->
// create authenticating OkHttpClient (credentials taken from account settings)
HttpClient.Builder(this, settings, AccountSettings(this, settings, account))
.setForeground(true)
.build().use { client ->
val httpClient = client.okHttpClient
// create authenticating OkHttpClient (credentials taken from account settings)
HttpClient.Builder(this, AccountSettings(this, account))
.setForeground(true)
.build().use { client ->
val httpClient = client.okHttpClient
// refresh home set list (from principal)
readPrincipal()?.let { principalUrl ->
Logger.log.fine("Querying principal $principalUrl for home sets")
queryHomeSets(httpClient, principalUrl)
// refresh home set list (from principal)
readPrincipal()?.let { principalUrl ->
Logger.log.fine("Querying principal $principalUrl for home sets")
queryHomeSets(httpClient, principalUrl)
}
// remember selected collections
val selectedCollections = HashSet<HttpUrl>()
collections.values
.filter { it.selected }
.forEach { (url, _) -> selectedCollections += url }
// now refresh collections (taken from home sets)
val itHomeSets = homeSets.iterator()
while (itHomeSets.hasNext()) {
val homeSetUrl = itHomeSets.next()
Logger.log.fine("Listing home set $homeSetUrl")
try {
DavResource(httpClient, homeSetUrl).propfind(1, *CollectionInfo.DAV_PROPERTIES) { response, _ ->
if (!response.isSuccess())
return@propfind
val info = CollectionInfo(response)
info.confirmed = true
Logger.log.log(Level.FINE, "Found collection", info)
if ((serviceType == Services.SERVICE_CARDDAV && info.type == CollectionInfo.Type.ADDRESS_BOOK) ||
(serviceType == Services.SERVICE_CALDAV && arrayOf(CollectionInfo.Type.CALENDAR, CollectionInfo.Type.WEBCAL).contains(info.type)))
collections[response.href] = info
}
} catch(e: HttpException) {
if (e.code in arrayOf(403, 404, 410))
// delete home set only if it was not accessible (40x)
itHomeSets.remove()
}
}
// remember selected collections
val selectedCollections = HashSet<HttpUrl>()
collections.values
.filter { it.selected }
.forEach { (url, _) -> selectedCollections += url }
// now refresh collections (taken from home sets)
val itHomeSets = homeSets.iterator()
while (itHomeSets.hasNext()) {
val homeSetUrl = itHomeSets.next()
Logger.log.fine("Listing home set $homeSetUrl")
// check/refresh unconfirmed collections
val itCollections = collections.entries.iterator()
while (itCollections.hasNext()) {
val (url, info) = itCollections.next()
if (!info.confirmed)
try {
DavResource(httpClient, homeSetUrl).propfind(1, *CollectionInfo.DAV_PROPERTIES) { response, _ ->
DavResource(httpClient, url).propfind(0, *CollectionInfo.DAV_PROPERTIES) { response, _ ->
if (!response.isSuccess())
return@propfind
val info = CollectionInfo(response)
info.confirmed = true
Logger.log.log(Level.FINE, "Found collection", info)
val collectionInfo = CollectionInfo(response)
collectionInfo.confirmed = true
if ((serviceType == Services.SERVICE_CARDDAV && info.type == CollectionInfo.Type.ADDRESS_BOOK) ||
(serviceType == Services.SERVICE_CALDAV && arrayOf(CollectionInfo.Type.CALENDAR, CollectionInfo.Type.WEBCAL).contains(info.type)))
collections[response.href] = info
// remove unusable collections
if ((serviceType == Services.SERVICE_CARDDAV && collectionInfo.type != CollectionInfo.Type.ADDRESS_BOOK) ||
(serviceType == Services.SERVICE_CALDAV && !arrayOf(CollectionInfo.Type.CALENDAR, CollectionInfo.Type.WEBCAL).contains(collectionInfo.type)) ||
(collectionInfo.type == CollectionInfo.Type.WEBCAL && collectionInfo.source == null))
itCollections.remove()
}
} catch(e: HttpException) {
if (e.code in arrayOf(403, 404, 410))
// delete home set only if it was not accessible (40x)
itHomeSets.remove()
// delete collection only if it was not accessible (40x)
itCollections.remove()
else
throw e
}
}
// check/refresh unconfirmed collections
val itCollections = collections.entries.iterator()
while (itCollections.hasNext()) {
val (url, info) = itCollections.next()
if (!info.confirmed)
try {
DavResource(httpClient, url).propfind(0, *CollectionInfo.DAV_PROPERTIES) { response, _ ->
if (!response.isSuccess())
return@propfind
val collectionInfo = CollectionInfo(response)
collectionInfo.confirmed = true
// remove unusable collections
if ((serviceType == Services.SERVICE_CARDDAV && collectionInfo.type != CollectionInfo.Type.ADDRESS_BOOK) ||
(serviceType == Services.SERVICE_CALDAV && !arrayOf(CollectionInfo.Type.CALENDAR, CollectionInfo.Type.WEBCAL).contains(collectionInfo.type)) ||
(collectionInfo.type == CollectionInfo.Type.WEBCAL && collectionInfo.source == null))
itCollections.remove()
}
} catch(e: HttpException) {
if (e.code in arrayOf(403, 404, 410))
// delete collection only if it was not accessible (40x)
itCollections.remove()
else
throw e
}
}
// restore selections
for (url in selectedCollections)
collections[url]?.let { it.selected = true }
}
// restore selections
for (url in selectedCollections)
collections[url]?.let { it.selected = true }
}
db.beginTransactionNonExclusive()

View file

@ -17,7 +17,8 @@ import at.bitfire.dav4android.Constants
import at.bitfire.dav4android.UrlUtils
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.Credentials
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Settings
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@ -67,7 +68,6 @@ class HttpClient private constructor(
class Builder(
val context: Context? = null,
val settings: ISettings? = null,
accountSettings: AccountSettings? = null,
val logger: java.util.logging.Logger = Logger.log
) {
@ -89,32 +89,36 @@ class HttpClient private constructor(
orig.addInterceptor(loggingInterceptor)
}
settings?.let {
context?.let {
val settings = Settings.getInstance(context)
// custom proxy support
try {
if (settings.getBoolean(App.OVERRIDE_PROXY, false)) {
if (settings.getBoolean(Settings.OVERRIDE_PROXY) == true) {
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)
settings.getString(Settings.OVERRIDE_PROXY_HOST)
?: Settings.OVERRIDE_PROXY_HOST_DEFAULT,
settings.getInt(Settings.OVERRIDE_PROXY_PORT)
?: Settings.OVERRIDE_PROXY_PORT_DEFAULT
)
val proxy = Proxy(Proxy.Type.HTTP, address)
orig.proxy(proxy)
Logger.log.log(Level.INFO, "Using proxy", proxy)
}
} catch(e: Exception) {
} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e)
}
context?.let {
if (BuildConfig.customCerts)
customCertManager(CustomCertManager(context, true, !settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)))
//if (BuildConfig.customCerts)
customCertManager(CustomCertManager(context, true,
!(settings.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES)
?: Settings.DISTRUST_SYSTEM_CERTIFICATES_DEFAULT)))
}
// use account settings for authentication
accountSettings?.let {
addAuthentication(null, it.credentials())
}
}
// use account settings for authentication
accountSettings?.let {
addAuthentication(null, it.credentials())
}
}
@ -176,6 +180,8 @@ class HttpClient private constructor(
var keyManager: KeyManager? = null
try {
certificateAlias?.let { alias ->
val context = requireNotNull(context)
// get client certificate and private key
val certs = KeyChain.getCertificateChain(context, alias) ?: return@let
val key = KeyChain.getPrivateKey(context, alias) ?: return@let

View file

@ -14,9 +14,9 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Process
import android.preference.PreferenceManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import android.util.Log
import at.bitfire.davdroid.R
import at.bitfire.davdroid.ui.AppSettingsActivity
import at.bitfire.davdroid.ui.NotificationUtils
@ -35,14 +35,14 @@ object Logger {
private lateinit var preferences: SharedPreferences
fun initialize(context: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(context)
fun initialize(appContext: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(appContext)
preferences.registerOnSharedPreferenceChangeListener { _, s ->
if (s == LOG_TO_EXTERNAL_STORAGE)
reinitialize(context.applicationContext)
reinitialize(appContext.applicationContext)
}
reinitialize(context.applicationContext)
reinitialize(appContext)
}
private fun reinitialize(context: Context) {

View file

@ -14,8 +14,8 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import android.preference.PreferenceManager
import at.bitfire.davdroid.App
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.ui.StartupDialogFragment
import java.util.logging.Level
@ -156,10 +156,10 @@ class ServiceDB {
db.query("settings", arrayOf("setting", "value"), null, null, null, null, null).use { cursor ->
while (cursor.moveToNext()) {
when (cursor.getString(0)) {
"distrustSystemCerts" -> edit.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, cursor.getInt(1) != 0)
"overrideProxy" -> edit.putBoolean(App.OVERRIDE_PROXY, cursor.getInt(1) != 0)
"overrideProxyHost" -> edit.putString(App.OVERRIDE_PROXY_HOST, cursor.getString(1))
"overrideProxyPort" -> edit.putInt(App.OVERRIDE_PROXY_PORT, cursor.getInt(1))
"distrustSystemCerts" -> edit.putBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES, cursor.getInt(1) != 0)
"overrideProxy" -> edit.putBoolean(Settings.OVERRIDE_PROXY, cursor.getInt(1) != 0)
"overrideProxyHost" -> edit.putString(Settings.OVERRIDE_PROXY_HOST, cursor.getString(1))
"overrideProxyPort" -> edit.putInt(Settings.OVERRIDE_PROXY_PORT, cursor.getInt(1))
StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED ->
edit.putBoolean(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, cursor.getInt(1) != 0)

View file

@ -5,7 +5,7 @@
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid
package at.bitfire.davdroid.settings
import android.accounts.Account
import android.accounts.AccountManager
@ -17,6 +17,7 @@ import android.os.Parcel
import android.os.RemoteException
import android.provider.CalendarContract
import android.provider.ContactsContract
import at.bitfire.davdroid.*
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.Credentials
@ -27,7 +28,6 @@ import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.CalendarStorageException
@ -47,7 +47,6 @@ import java.util.logging.Level
*/
class AccountSettings(
val context: Context,
val settings: ISettings,
val account: Account
) {
@ -60,6 +59,7 @@ class AccountSettings(
const val KEY_CERTIFICATE_ALIAS = "certificate_alias"
const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false)
const val WIFI_ONLY_DEFAULT = false
const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids" // restrict sync to specific WiFi SSIDs
/** Time range limitation to the past [in days]
@ -103,9 +103,10 @@ class AccountSettings(
}
}
val accountManager: AccountManager = AccountManager.get(context)
val settings = Settings.getInstance(context)
init {
synchronized(AccountSettings::class.java) {
@ -160,14 +161,14 @@ class AccountSettings(
}
fun getSyncWifiOnly() = if (settings.has(KEY_WIFI_ONLY))
settings.getBoolean(KEY_WIFI_ONLY, false)
settings.getBoolean(KEY_WIFI_ONLY) ?: WIFI_ONLY_DEFAULT
else
accountManager.getUserData(account, KEY_WIFI_ONLY) != null
fun setSyncWiFiOnly(wiFiOnly: Boolean) =
accountManager.setUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null)
fun getSyncWifiOnlySSIDs(): List<String>? = (if (settings.has(KEY_WIFI_ONLY_SSIDS))
settings.getString(KEY_WIFI_ONLY_SSIDS, null)
settings.getString(KEY_WIFI_ONLY_SSIDS)
else
accountManager.getUserData(account, KEY_WIFI_ONLY_SSIDS))?.split(',')
fun setSyncWifiOnlySSIDs(ssids: List<String>?) =
@ -189,14 +190,14 @@ class AccountSettings(
accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, (days ?: -1).toString())
fun getManageCalendarColors() = if (settings.has(KEY_MANAGE_CALENDAR_COLORS))
settings.getBoolean(KEY_MANAGE_CALENDAR_COLORS, false)
settings.getBoolean(KEY_MANAGE_CALENDAR_COLORS) ?: false
else
accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null
fun setManageCalendarColors(manage: Boolean) =
accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0")
fun getEventColors() = if (settings.has(KEY_EVENT_COLORS))
settings.getBoolean(KEY_EVENT_COLORS, false)
settings.getBoolean(KEY_EVENT_COLORS) ?: false
else
accountManager.getUserData(account, KEY_EVENT_COLORS) != null
fun setEventColors(useColors: Boolean) =
@ -205,7 +206,7 @@ class AccountSettings(
// CardDAV settings
fun getGroupMethod(): GroupMethod {
val name = settings.getString(KEY_CONTACT_GROUP_METHOD, null) ?:
val name = settings.getString(KEY_CONTACT_GROUP_METHOD) ?:
accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD)
if (name != null)
try {
@ -224,7 +225,7 @@ class AccountSettings(
// update from previous account settings
private fun update(baseVersion: Int) {
for (toVersion in baseVersion+1 .. CURRENT_VERSION) {
for (toVersion in baseVersion+1 ..CURRENT_VERSION) {
val fromVersion = toVersion-1
Logger.log.info("Updating account ${account.name} from version $fromVersion to $toVersion")
try {
@ -519,7 +520,7 @@ class AccountSettings(
try {
val addr = LocalAddressBook(context, account, provider)
// until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
// until now, ContactsContract.settings.UNGROUPED_VISIBLE was not set explicitly
val values = ContentValues()
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1)
addr.settings = values

View file

@ -8,32 +8,31 @@
package at.bitfire.davdroid.settings
import at.bitfire.davdroid.App
import android.content.Context
open class DefaultsProvider(
private val allowOverride: Boolean = true
): Provider {
): SettingsProvider {
open val booleanDefaults = mapOf(
Pair(App.DISTRUST_SYSTEM_CERTIFICATES, false),
Pair(App.OVERRIDE_PROXY, false)
Pair(Settings.DISTRUST_SYSTEM_CERTIFICATES, Settings.DISTRUST_SYSTEM_CERTIFICATES_DEFAULT),
Pair(Settings.OVERRIDE_PROXY, Settings.OVERRIDE_PROXY_DEFAULT)
)
open val intDefaults = mapOf(
Pair(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT)
Pair(Settings.OVERRIDE_PROXY_PORT, Settings.OVERRIDE_PROXY_PORT_DEFAULT)
)
open val longDefaults = mapOf<String, Long>()
open val stringDefaults = mapOf(
Pair(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT)
Pair(Settings.OVERRIDE_PROXY_HOST, Settings.OVERRIDE_PROXY_HOST_DEFAULT)
)
override fun close() {
override fun forceReload() {
}
override fun forceReload() {
override fun close() {
}
@ -71,4 +70,9 @@ open class DefaultsProvider(
override fun remove(key: String) = false
class Factory : ISettingsProviderFactory {
override fun getProviders(context: Context) = listOf(DefaultsProvider())
}
}

View file

@ -0,0 +1,17 @@
/*
* 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 at.bitfire.davdroid.settings
import android.content.Context
interface ISettingsProviderFactory {
fun getProviders(context: Context): List<SettingsProvider>
}

View file

@ -8,62 +8,79 @@
package at.bitfire.davdroid.settings
import android.annotation.TargetApi
import android.app.Service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import at.bitfire.davdroid.log.Logger
import java.lang.ref.WeakReference
import java.util.*
import java.util.logging.Level
class Settings: Service(), Provider.Observer {
class Settings(
appContext: Context
) {
private val providers = LinkedList<Provider>()
private val observers = LinkedList<WeakReference<ISettingsObserver>>()
companion object {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate() {
Logger.log.info("Initializing Settings service")
// settings keys and default values
const val DISTRUST_SYSTEM_CERTIFICATES = "distrust_system_certs"
const val DISTRUST_SYSTEM_CERTIFICATES_DEFAULT = false
const val OVERRIDE_PROXY = "override_proxy"
const val OVERRIDE_PROXY_DEFAULT = false
const val OVERRIDE_PROXY_HOST = "override_proxy_host"
const val OVERRIDE_PROXY_PORT = "override_proxy_port"
// always add a defaults provider first
providers.add(DefaultsProvider())
const val OVERRIDE_PROXY_HOST_DEFAULT = "localhost"
const val OVERRIDE_PROXY_PORT_DEFAULT = 8118
private var singleton: Settings? = null
fun getInstance(context: Context): Settings {
singleton?.let { return it }
val newInstance = Settings(context.applicationContext)
singleton = newInstance
return newInstance
}
// always add a provider for local preferences
providers.add(SharedPreferencesProvider(this))
}
override fun onDestroy() {
Logger.log.info("Shutting down Settings service")
providers.forEach { it.close() }
providers.clear()
}
private val providers = LinkedList<SettingsProvider>()
private val observers = LinkedList<WeakReference<OnChangeListener>>()
override fun onTrimMemory(level: Int) {
stopSelf()
init {
ServiceLoader.load(ISettingsProviderFactory::class.java).forEach { factory ->
providers.addAll(factory.getProviders(appContext))
}
}
fun forceReload() {
providers.forEach { it.forceReload() }
providers.forEach {
it.forceReload()
}
onSettingsChanged()
}
override fun onReload() {
observers.forEach {
Handler(Looper.getMainLooper()).post {
it.get()?.onSettingsChanged()
}
/*** OBSERVERS ***/
fun addOnChangeListener(observer: OnChangeListener) {
observers += WeakReference(observer)
}
fun removeOnChangeListener(observer: OnChangeListener) {
observers.removeAll { it.get() == null || it.get() == observer }
}
fun onSettingsChanged() {
observers.mapNotNull { it.get() }.forEach {
it.onSettingsChanged()
}
}
private fun has(key: String): Boolean {
/*** SETTINGS ACCESS ***/
fun has(key: String): Boolean {
Logger.log.fine("Looking for setting $key")
var result = false
for (provider in providers)
@ -83,7 +100,7 @@ class Settings: Service(), Provider.Observer {
return result
}
private fun<T> getValue(key: String, reader: (Provider) -> Pair<T?, Boolean>): T? {
private fun<T> getValue(key: String, reader: (SettingsProvider) -> Pair<T?, Boolean>): T? {
Logger.log.fine("Looking up setting $key")
var result: T? = null
for (provider in providers)
@ -124,7 +141,7 @@ class Settings: Service(), Provider.Observer {
return false
}
private fun<T> putValue(key: String, value: T?, writer: (Provider) -> Boolean): Boolean {
private fun<T> putValue(key: String, value: T?, writer: (SettingsProvider) -> Boolean): Boolean {
Logger.log.fine("Trying to write setting $key = $value")
for (provider in providers) {
val (writable, further) = provider.isWritable(key)
@ -161,115 +178,8 @@ class Settings: Service(), Provider.Observer {
}
val binder = object: ISettings.Stub() {
override fun forceReload() =
this@Settings.forceReload()
override fun has(key: String) =
this@Settings.has(key)
override fun getBoolean(key: String, defaultValue: Boolean) =
this@Settings.getBoolean(key) ?: defaultValue
override fun getInt(key: String, defaultValue: Int) =
this@Settings.getInt(key) ?: defaultValue
override fun getLong(key: String, defaultValue: Long) =
this@Settings.getLong(key) ?: defaultValue
override fun getString(key: String, defaultValue: String?) =
this@Settings.getString(key) ?: defaultValue
override fun isWritable(key: String) =
this@Settings.isWritable(key)
override fun remove(key: String) =
this@Settings.remove(key)
override fun putBoolean(key: String, value: Boolean) =
this@Settings.putBoolean(key, value)
override fun putString(key: String, value: String?) =
this@Settings.putString(key, value)
override fun putInt(key: String, value: Int) =
this@Settings.putInt(key, value)
override fun putLong(key: String, value: Long) =
this@Settings.putLong(key, value)
override fun registerObserver(observer: ISettingsObserver) {
observers += WeakReference(observer)
}
override fun unregisterObserver(observer: ISettingsObserver) {
observers.removeAll { it.get() == observer }
}
}
override fun onBind(intent: Intent?) = binder
class Stub(
delegate: ISettings,
private val context: Context,
private val serviceConn: ServiceConnection?
): ISettings by delegate, AutoCloseable {
override fun close() {
try {
context.unbindService(serviceConn)
} catch(e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't unbind Settings service", e)
}
}
}
companion object {
fun getInstance(context: Context): Stub? {
if (Looper.getMainLooper().thread == Thread.currentThread())
throw IllegalStateException("Must not be called from main thread")
var service: ISettings? = null
val serviceLock = Object()
val serviceConn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
synchronized(serviceLock) {
service = ISettings.Stub.asInterface(binder)
serviceLock.notify()
}
}
override fun onServiceDisconnected(name: ComponentName) {
service = null
}
}
if (!context.bindService(Intent(context, Settings::class.java), serviceConn, Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT))
return null
synchronized(serviceLock) {
if (service == null)
try {
serviceLock.wait()
} catch(e: InterruptedException) {
}
if (service == null) {
try {
context.unbindService(serviceConn)
} catch (e: IllegalArgumentException) {
}
return null
}
}
return Stub(service!!, context, serviceConn)
}
interface OnChangeListener {
fun onSettingsChanged()
}
}

View file

@ -8,11 +8,10 @@
package at.bitfire.davdroid.settings
import java.io.Closeable
interface Provider: Closeable {
interface SettingsProvider {
fun forceReload()
fun close()
fun has(key: String): Pair<Boolean, Boolean>
@ -30,9 +29,4 @@ interface Provider: Closeable {
fun remove(key: String): Boolean
interface Observer {
fun onReload()
}
}

View file

@ -16,8 +16,8 @@ import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.ServiceDB
class SharedPreferencesProvider(
context: Context
): Provider {
val context: Context
): SettingsProvider, SharedPreferences.OnSharedPreferenceChangeListener {
companion object {
private const val META_VERSION = "version"
@ -34,14 +34,21 @@ class SharedPreferencesProvider(
firstCall(context)
meta.edit().putInt(META_VERSION, CURRENT_VERSION).apply()
}
}
override fun close() {
preferences.registerOnSharedPreferenceChangeListener(this)
}
override fun forceReload() {
}
override fun close() {
preferences.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
Settings.getInstance(context).onSettingsChanged()
}
override fun has(key: String) =
Pair(preferences.contains(key), true)
@ -117,4 +124,9 @@ class SharedPreferencesProvider(
ServiceDB.OpenHelper(context).use { it.readableDatabase }
}
class Factory : ISettingsProviderFactory {
override fun getProviders(context: Context) = listOf(SharedPreferencesProvider(context))
}
}

View file

@ -16,13 +16,12 @@ import android.os.Build
import android.os.Bundle
import android.provider.ContactsContract
import androidx.core.content.ContextCompat
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.model.ServiceDB.Collections
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.AccountActivity
import okhttp3.HttpUrl
import java.util.logging.Level
@ -36,9 +35,9 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
context: Context
) : SyncAdapter(context) {
override fun sync(settings: ISettings, account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
override fun sync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
try {
val accountSettings = AccountSettings(context, settings, account)
val accountSettings = AccountSettings(context, account)
/* don't run sync if
- sync conditions (e.g. "sync only in WiFi") are not met AND

View file

@ -18,14 +18,13 @@ import at.bitfire.dav4android.DavResponseCallback
import at.bitfire.dav4android.Response
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.property.*
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.resource.LocalEvent
import at.bitfire.davdroid.resource.LocalResource
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.InvalidCalendarException
import okhttp3.HttpUrl
@ -41,14 +40,13 @@ import java.util.logging.Level
*/
class CalendarSyncManager(
context: Context,
settings: ISettings,
account: Account,
accountSettings: AccountSettings,
extras: Bundle,
authority: String,
syncResult: SyncResult,
localCalendar: LocalCalendar
): SyncManager<LocalEvent, LocalCalendar, DavCalendar>(context, settings, account, accountSettings, extras, authority, syncResult, localCalendar) {
): SyncManager<LocalEvent, LocalCalendar, DavCalendar>(context, account, accountSettings, extras, authority, syncResult, localCalendar) {
override fun prepare(): Boolean {
collectionURL = HttpUrl.parse(localCollection.name ?: return false) ?: return false

View file

@ -12,13 +12,12 @@ import android.content.*
import android.database.DatabaseUtils
import android.os.Bundle
import android.provider.CalendarContract
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.model.ServiceDB.Collections
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.AndroidCalendar
import okhttp3.HttpUrl
import java.util.logging.Level
@ -32,9 +31,9 @@ class CalendarsSyncAdapterService: SyncAdapterService() {
context: Context
): SyncAdapter(context) {
override fun sync(settings: ISettings, account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
override fun sync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
try {
val accountSettings = AccountSettings(context, settings, account)
val accountSettings = AccountSettings(context, account)
/* don't run sync if
- sync conditions (e.g. "sync only in WiFi") are not met AND
@ -52,7 +51,7 @@ class CalendarsSyncAdapterService: SyncAdapterService() {
for (calendar in AndroidCalendar.find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null)) {
Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}")
CalendarSyncManager(context, settings, account, accountSettings, extras, authority, syncResult, calendar).use {
CalendarSyncManager(context, account, accountSettings, extras, authority, syncResult, calendar).use {
it.performSync()
}
}

View file

@ -15,10 +15,9 @@ import android.content.Context
import android.content.SyncResult
import android.os.Bundle
import android.provider.ContactsContract
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import java.util.logging.Level
class ContactsSyncAdapterService: SyncAdapterService() {
@ -34,10 +33,10 @@ class ContactsSyncAdapterService: SyncAdapterService() {
context: Context
): SyncAdapter(context) {
override fun sync(settings: ISettings, account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
override fun sync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
try {
val addressBook = LocalAddressBook(context, account, provider)
val accountSettings = AccountSettings(context, settings, addressBook.mainAccount)
val accountSettings = AccountSettings(context, addressBook.mainAccount)
// handle group method change
val groupMethod = accountSettings.getGroupMethod().name
@ -61,7 +60,7 @@ class ContactsSyncAdapterService: SyncAdapterService() {
Logger.log.info("Synchronizing address book: ${addressBook.url}")
Logger.log.info("Taking settings from: ${addressBook.mainAccount}")
ContactsSyncManager(context, settings, account, accountSettings, extras, authority, syncResult, provider, addressBook).use {
ContactsSyncManager(context, account, accountSettings, extras, authority, syncResult, provider, addressBook).use {
it.performSync()
}
} catch(e: Exception) {

View file

@ -20,14 +20,13 @@ import at.bitfire.dav4android.DavResponseCallback
import at.bitfire.dav4android.Response
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.property.*
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.*
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.NotificationUtils
import at.bitfire.vcard4android.BatchOperation
import at.bitfire.vcard4android.Contact
@ -76,7 +75,6 @@ import java.util.logging.Level
*/
class ContactsSyncManager(
context: Context,
settings: ISettings,
account: Account,
accountSettings: AccountSettings,
extras: Bundle,
@ -84,7 +82,7 @@ class ContactsSyncManager(
syncResult: SyncResult,
val provider: ContentProviderClient,
localAddressBook: LocalAddressBook
): SyncManager<LocalAddress, LocalAddressBook, DavAddressBook>(context, settings, account, accountSettings, extras, authority, syncResult, localAddressBook) {
): SyncManager<LocalAddress, LocalAddressBook, DavAddressBook>(context, account, accountSettings, extras, authority, syncResult, localAddressBook) {
companion object {
infix fun <T> Set<T>.disjunct(other: Set<T>) = (this - other) union (other - this)

View file

@ -21,11 +21,9 @@ import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.AccountActivity
import at.bitfire.davdroid.ui.AccountSettingsActivity
import at.bitfire.davdroid.ui.NotificationUtils
@ -52,7 +50,7 @@ abstract class SyncAdapterService: Service() {
context: Context
): AbstractThreadedSyncAdapter(context, false) {
abstract fun sync(settings: ISettings, account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult)
abstract fun sync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult)
override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
Logger.log.log(Level.INFO, "$authority sync of $account has been initiated", extras.keySet().joinToString(", "))
@ -71,19 +69,8 @@ abstract class SyncAdapterService: Service() {
// required for dav4android (ServiceLoader)
Thread.currentThread().contextClassLoader = context.classLoader
// load app settings
Settings.getInstance(context).use { settings ->
if (settings == null) {
syncResult.databaseError = true
Logger.log.severe("Couldn't connect to Settings service, aborting sync")
return
}
//if (runSync) {
SyncManager.cancelNotifications(NotificationManagerCompat.from(context), authority, account)
sync(settings, account, extras, authority, provider, syncResult)
//}
}
SyncManager.cancelNotifications(NotificationManagerCompat.from(context), authority, account)
sync(account, extras, authority, provider, syncResult)
} finally {
synchronized(runningSyncs) {
runningSyncs.removeAll { it.get() == null || it.get() == currentSync }

View file

@ -30,7 +30,7 @@ import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.*
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.AccountSettingsActivity
import at.bitfire.davdroid.ui.DebugInfoActivity
import at.bitfire.davdroid.ui.NotificationUtils
@ -54,7 +54,6 @@ import javax.net.ssl.SSLHandshakeException
@Suppress("MemberVisibilityCanBePrivate")
abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: LocalCollection<ResourceType>, RemoteType: DavCollection>(
val context: Context,
val settings: ISettings,
val account: Account,
val accountSettings: AccountSettings,
val extras: Bundle,
@ -70,8 +69,8 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
companion object {
val MAX_PROCESSING_THREADS = Math.max(Runtime.getRuntime().availableProcessors(), 4)
val MAX_DOWNLOAD_THREADS = Math.max(Runtime.getRuntime().availableProcessors(), 4)
val MAX_PROCESSING_THREADS = Math.min(Runtime.getRuntime().availableProcessors()/2, 1)
val MAX_DOWNLOAD_THREADS = Math.max(Runtime.getRuntime().availableProcessors(), 2)
const val MAX_MULTIGET_RESOURCES = 10
fun cancelNotifications(manager: NotificationManagerCompat, authority: String, account: Account) =
@ -90,7 +89,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
protected val notificationManager = NotificationManagerCompat.from(context)
protected val notificationTag = notificationTag(authority, mainAccount)
protected val httpClient = HttpClient.Builder(context, settings, accountSettings).build()
protected val httpClient = HttpClient.Builder(context, accountSettings).build()
protected lateinit var collectionURL: HttpUrl
protected lateinit var davCollection: RemoteType

View file

@ -17,7 +17,6 @@ import android.net.Uri
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
@ -25,7 +24,7 @@ import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.model.ServiceDB.Collections
import at.bitfire.davdroid.model.ServiceDB.Services
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.NotificationUtils
import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.TaskProvider
@ -45,10 +44,10 @@ class TasksSyncAdapterService: SyncAdapterService() {
context: Context
): SyncAdapter(context) {
override fun sync(settings: ISettings, account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
override fun sync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
try {
val taskProvider = TaskProvider.fromProviderClient(context, provider)
val accountSettings = AccountSettings(context, settings, account)
val accountSettings = AccountSettings(context, account)
/* don't run sync if
- sync conditions (e.g. "sync only in WiFi") are not met AND
- this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions)
@ -60,7 +59,7 @@ class TasksSyncAdapterService: SyncAdapterService() {
for (taskList in AndroidTaskList.find(account, taskProvider, LocalTaskList.Factory, "${TaskContract.TaskLists.SYNC_ENABLED}!=0", null)) {
Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]")
TasksSyncManager(context, settings, account, accountSettings, extras, authority, syncResult, taskList).use {
TasksSyncManager(context, account, accountSettings, extras, authority, syncResult, taskList).use {
it.performSync()
}
}

View file

@ -21,14 +21,13 @@ import at.bitfire.dav4android.property.CalendarData
import at.bitfire.dav4android.property.GetCTag
import at.bitfire.dav4android.property.GetETag
import at.bitfire.dav4android.property.SyncToken
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.LocalResource
import at.bitfire.davdroid.resource.LocalTask
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.InvalidCalendarException
import at.bitfire.ical4android.Task
import okhttp3.HttpUrl
@ -43,14 +42,13 @@ import java.util.logging.Level
*/
class TasksSyncManager(
context: Context,
settings: ISettings,
account: Account,
accountSettings: AccountSettings,
extras: Bundle,
authority: String,
syncResult: SyncResult,
localCollection: LocalTaskList
): SyncManager<LocalTask, LocalTaskList, DavCalendar>(context, settings, account, accountSettings, extras, authority, syncResult, localCollection) {
): SyncManager<LocalTask, LocalTaskList, DavCalendar>(context, account, accountSettings, extras, authority, syncResult, localCollection) {
override fun prepare(): Boolean {
collectionURL = HttpUrl.parse(localCollection.syncId ?: return false) ?: return false

View file

@ -10,9 +10,7 @@ package at.bitfire.davdroid.ui
import android.Manifest
import android.accounts.Account
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.SyncStatusObserver
import android.content.pm.PackageManager
@ -29,15 +27,12 @@ import androidx.core.app.ActivityCompat
import androidx.core.app.NavUtils
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.preference.*
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.model.Credentials
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.TaskProvider
import at.bitfire.vcard4android.GroupMethod
@ -76,26 +71,45 @@ class AccountSettingsActivity: AppCompatActivity() {
false
class AccountSettingsFragment: PreferenceFragmentCompat(), LoaderManager.LoaderCallbacks<Pair<ISettings, AccountSettings>> {
class AccountSettingsFragment: PreferenceFragmentCompat(), SyncStatusObserver, Settings.OnChangeListener {
private lateinit var settings: Settings
lateinit var account: Account
private var statusChangeListener: Any? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
settings = Settings.getInstance(requireActivity())
account = arguments!!.getParcelable(EXTRA_ACCOUNT)!!
LoaderManager.getInstance(this).initLoader(0, arguments, this)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.settings_account)
}
override fun onCreateLoader(id: Int, args: Bundle?) =
AccountSettingsLoader(requireActivity(), args!!.getParcelable(EXTRA_ACCOUNT)!!)
override fun onResume() {
super.onResume()
@SuppressLint("Recycle")
override fun onLoadFinished(loader: Loader<Pair<ISettings, AccountSettings>>, result: Pair<ISettings, AccountSettings>?) {
val (settings, accountSettings) = result ?: return
statusChangeListener = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this)
settings.addOnChangeListener(this)
reload()
}
override fun onPause() {
super.onPause()
statusChangeListener?.let {
ContentResolver.removeStatusChangeListener(it)
statusChangeListener = null
}
settings.removeOnChangeListener(this)
}
override fun onStatusChanged(which: Int) = reload()
override fun onSettingsChanged() = reload()
fun reload() {
val accountSettings = AccountSettings(requireActivity(), account)
// preference group: authentication
val prefUserName = findPreference("username") as EditTextPreference
@ -110,14 +124,14 @@ class AccountSettingsActivity: AppCompatActivity() {
prefUserName.text = credentials.userName
prefUserName.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.credentials(Credentials(newValue as String, credentials.password))
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
prefPassword.isVisible = true
prefPassword.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.credentials(Credentials(credentials.userName, newValue as String))
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
@ -133,7 +147,7 @@ class AccountSettingsActivity: AppCompatActivity() {
KeyChain.choosePrivateKeyAlias(requireActivity(), { alias ->
accountSettings.credentials(Credentials(certificateAlias = alias))
Handler(Looper.getMainLooper()).post {
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
}
}, null, null, null, -1, credentials.certificateAlias)
true
@ -157,7 +171,7 @@ class AccountSettingsActivity: AppCompatActivity() {
it.summary = getString(R.string.settings_sync_summary_periodically, syncIntervalContacts / 60)
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.setSyncInterval(getString(R.string.address_books_authority), (newValue as String).toLong())
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
} else
@ -174,7 +188,7 @@ class AccountSettingsActivity: AppCompatActivity() {
it.summary = getString(R.string.settings_sync_summary_periodically, syncIntervalCalendars / 60)
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.setSyncInterval(CalendarContract.AUTHORITY, (newValue as String).toLong())
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
} else
@ -191,7 +205,7 @@ class AccountSettingsActivity: AppCompatActivity() {
it.summary = getString(R.string.settings_sync_summary_periodically, syncIntervalTasks / 60)
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, (newValue as String).toLong())
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
} else
@ -203,7 +217,7 @@ class AccountSettingsActivity: AppCompatActivity() {
prefWifiOnly.isChecked = accountSettings.getSyncWifiOnly()
prefWifiOnly.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, wifiOnly ->
accountSettings.setSyncWiFiOnly(wifiOnly as Boolean)
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
@ -216,7 +230,7 @@ class AccountSettingsActivity: AppCompatActivity() {
prefWifiOnlySSIDs.setSummary(R.string.settings_sync_wifi_only_ssids_off)
prefWifiOnlySSIDs.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.setSyncWifiOnlySSIDs((newValue as String).split(',').mapNotNull { StringUtils.trimToNull(it) }.distinct())
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
@ -245,7 +259,7 @@ class AccountSettingsActivity: AppCompatActivity() {
.setPositiveButton(android.R.string.ok) { _, _ ->
// change group method
accountSettings.setGroupMethod(GroupMethod.valueOf(groupMethod as String))
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
// reload all contacts
val args = Bundle(1)
@ -298,7 +312,7 @@ class AccountSettingsActivity: AppCompatActivity() {
}
}
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
} else
@ -312,7 +326,7 @@ class AccountSettingsActivity: AppCompatActivity() {
it.isChecked = accountSettings.getManageCalendarColors()
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
accountSettings.setManageCalendarColors(newValue as Boolean)
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
false
}
} else
@ -327,7 +341,7 @@ class AccountSettingsActivity: AppCompatActivity() {
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) {
accountSettings.setEventColors(true)
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
} else
AlertDialog.Builder(requireActivity())
.setIcon(R.drawable.ic_error_dark)
@ -336,7 +350,7 @@ class AccountSettingsActivity: AppCompatActivity() {
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
accountSettings.setEventColors(false)
LoaderManager.getInstance(this).restartLoader(0, arguments, this)
reload()
}
.show()
false
@ -346,52 +360,6 @@ class AccountSettingsActivity: AppCompatActivity() {
}
}
override fun onLoaderReset(loader: Loader<Pair<ISettings, AccountSettings>>) {
}
}
class AccountSettingsLoader(
context: Context,
val account: Account
): SettingsLoader<Pair<ISettings, AccountSettings>?>(context), SyncStatusObserver {
private var listenerHandle: Any? = null
override fun onStartLoading() {
super.onStartLoading()
if (listenerHandle == null)
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this)
}
override fun onReset() {
super.onReset()
listenerHandle?.let {
ContentResolver.removeStatusChangeListener(it)
listenerHandle = null
}
}
override fun loadInBackground(): Pair<ISettings, AccountSettings>? {
settings?.let { settings ->
try {
return Pair(
settings,
AccountSettings(context, settings, account)
)
} catch (e: InvalidAccountException) {
}
}
return null
}
override fun onStatusChanged(which: Int) {
onContentChanged()
}
}
}

View file

@ -10,7 +10,6 @@ package at.bitfire.davdroid.ui
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.SyncStatusObserver
import android.os.Bundle
@ -18,39 +17,48 @@ import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import at.bitfire.davdroid.App
import at.bitfire.davdroid.R
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.ui.setup.LoginActivity
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.accounts_content.*
import kotlinx.android.synthetic.main.activity_accounts.*
import kotlinx.android.synthetic.main.activity_accounts.view.*
class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, LoaderManager.LoaderCallbacks<AccountsActivity.Settings>, SyncStatusObserver {
class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, SyncStatusObserver {
companion object {
val accountsDrawerHandler = DefaultAccountsDrawerHandler()
private const val fragTagStartup = "startup"
const val fragTagStartup = "startup"
}
private lateinit var settings: Settings
private var syncStatusSnackbar: Snackbar? = null
private var syncStatusObserver: Any? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_accounts)
settings = Settings.getInstance(this)
setContentView(R.layout.activity_accounts)
setSupportActionBar(toolbar)
if (supportFragmentManager.findFragmentByTag(fragTagStartup) == null) {
val ft = supportFragmentManager.beginTransaction()
StartupDialogFragment.getStartupDialogs(this).forEach { ft.add(it, fragTagStartup) }
ft.commit()
}
fab.setOnClickListener {
startActivity(Intent(this, LoginActivity::class.java))
}
fab.show()
accountsDrawerHandler.initMenu(this, drawer_layout.nav_view.menu)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
@ -58,38 +66,6 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele
nav_view.setNavigationItemSelectedListener(this)
nav_view.itemIconTintList = null
/* When the DAVdroid main activity is started, start a Settings service that stays in memory
for better performance. The service stops itself when memory is trimmed. */
val settingsIntent = Intent(this, Settings::class.java)
startService(settingsIntent)
val args = Bundle(1)
LoaderManager.getInstance(this).initLoader(0, args, this)
}
override fun onCreateLoader(code: Int, args: Bundle?) =
SettingsLoader(this)
override fun onLoadFinished(loader: Loader<Settings>, result: Settings?) {
if (result == null)
return
if (supportFragmentManager.findFragmentByTag(fragTagStartup) == null) {
val ft = supportFragmentManager.beginTransaction()
StartupDialogFragment.getStartupDialogs(this, result.settings).forEach { ft.add(it, fragTagStartup) }
ft.commit()
}
nav_view?.menu?.let {
accountsDrawerHandler.onSettingsChanged(result.settings, it)
}
}
override fun onLoaderReset(loader: Loader<Settings>) {
nav_view?.menu?.let {
accountsDrawerHandler.onSettingsChanged(null, it)
}
}
override fun onResume() {
@ -139,27 +115,4 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele
return processed
}
class Settings(
val settings: ISettings
)
class SettingsLoader(
context: Context
): at.bitfire.davdroid.ui.SettingsLoader<Settings>(context) {
override fun loadInBackground(): Settings? {
settings?.let {
val accountManager = AccountManager.get(context)
val accounts = accountManager.getAccountsByType(context.getString(R.string.account_type))
return Settings(
it
)
}
return null
}
}
}

View file

@ -8,26 +8,16 @@
package at.bitfire.davdroid.ui
import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.os.Process
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.davdroid.App
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.ISettingsObserver
import at.bitfire.davdroid.settings.Settings
import com.google.android.material.snackbar.Snackbar
import java.net.URI
@ -52,43 +42,11 @@ class AppSettingsActivity: AppCompatActivity() {
}
class SettingsFragment: PreferenceFragmentCompat() {
val observer = object: ISettingsObserver.Stub() {
override fun onSettingsChanged() {
loadSettings()
}
}
var settings: ISettings? = null
var settingsSvc: ServiceConnection? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val serviceConn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
settings = ISettings.Stub.asInterface(binder)
settings?.registerObserver(observer)
loadSettings()
}
override fun onServiceDisconnected(name: ComponentName) {
settings?.unregisterObserver(observer)
settings = null
}
}
if (activity!!.bindService(Intent(activity, Settings::class.java), serviceConn, Context.BIND_AUTO_CREATE))
settingsSvc = serviceConn
}
override fun onDestroy() {
super.onDestroy()
settingsSvc?.let { activity!!.unbindService(it) }
}
class SettingsFragment: PreferenceFragmentCompat(), Settings.OnChangeListener {
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
addPreferencesFromResource(R.xml.settings_app)
loadSettings()
// UI settings
val prefResetHints = findPreference("reset_hints")
@ -98,7 +56,7 @@ class AppSettingsActivity: AppCompatActivity() {
}
// security settings
val prefDistrustSystemCerts = findPreference(App.DISTRUST_SYSTEM_CERTIFICATES)
val prefDistrustSystemCerts = findPreference(Settings.DISTRUST_SYSTEM_CERTIFICATES)
prefDistrustSystemCerts.isVisible = BuildConfig.customCerts
prefDistrustSystemCerts.isEnabled = true
@ -114,23 +72,23 @@ class AppSettingsActivity: AppCompatActivity() {
}
private fun loadSettings() {
val settings = requireNotNull(settings)
val settings = Settings.getInstance(requireActivity())
// connection settings
val prefOverrideProxy = findPreference(App.OVERRIDE_PROXY) as SwitchPreferenceCompat
prefOverrideProxy.isChecked = settings.getBoolean(App.OVERRIDE_PROXY, false)
prefOverrideProxy.isEnabled = settings.isWritable(App.OVERRIDE_PROXY)
val prefOverrideProxy = findPreference(Settings.OVERRIDE_PROXY) as SwitchPreferenceCompat
prefOverrideProxy.isChecked = settings.getBoolean(Settings.OVERRIDE_PROXY) ?: Settings.OVERRIDE_PROXY_DEFAULT
prefOverrideProxy.isEnabled = settings.isWritable(Settings.OVERRIDE_PROXY)
val prefProxyHost = findPreference(App.OVERRIDE_PROXY_HOST) as EditTextPreference
prefProxyHost.isEnabled = settings.isWritable(App.OVERRIDE_PROXY_HOST)
val proxyHost = settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT)
val prefProxyHost = findPreference(Settings.OVERRIDE_PROXY_HOST) as EditTextPreference
prefProxyHost.isEnabled = settings.isWritable(Settings.OVERRIDE_PROXY_HOST)
val proxyHost = settings.getString(Settings.OVERRIDE_PROXY_HOST) ?: Settings.OVERRIDE_PROXY_HOST_DEFAULT
prefProxyHost.text = proxyHost
prefProxyHost.summary = proxyHost
prefProxyHost.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val host = newValue as String
try {
URI(null, host, null, null)
settings.putString(App.OVERRIDE_PROXY_HOST, host)
settings.putString(Settings.OVERRIDE_PROXY_HOST, host)
prefProxyHost.summary = host
true
} catch(e: URISyntaxException) {
@ -139,16 +97,16 @@ class AppSettingsActivity: AppCompatActivity() {
}
}
val prefProxyPort = findPreference(App.OVERRIDE_PROXY_PORT) as EditTextPreference
prefProxyHost.isEnabled = settings.isWritable(App.OVERRIDE_PROXY_PORT)
val proxyPort = settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT)
val prefProxyPort = findPreference(Settings.OVERRIDE_PROXY_PORT) as EditTextPreference
prefProxyHost.isEnabled = settings.isWritable(Settings.OVERRIDE_PROXY_PORT)
val proxyPort = settings.getInt(Settings.OVERRIDE_PROXY_PORT) ?: Settings.OVERRIDE_PROXY_PORT_DEFAULT
prefProxyPort.text = proxyPort.toString()
prefProxyPort.summary = proxyPort.toString()
prefProxyPort.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
try {
val port = Integer.parseInt(newValue as String)
if (port in 1..65535) {
settings.putInt(App.OVERRIDE_PROXY_PORT, port)
settings.putInt(Settings.OVERRIDE_PROXY_PORT, port)
prefProxyPort.text = port.toString()
prefProxyPort.summary = port.toString()
true
@ -160,32 +118,27 @@ class AppSettingsActivity: AppCompatActivity() {
}
// security settings
val prefDistrustSystemCerts = findPreference(App.DISTRUST_SYSTEM_CERTIFICATES) as SwitchPreferenceCompat
prefDistrustSystemCerts.isChecked = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)
val prefDistrustSystemCerts = findPreference(Settings.DISTRUST_SYSTEM_CERTIFICATES) as SwitchPreferenceCompat
prefDistrustSystemCerts.isChecked = settings.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES) ?: Settings.DISTRUST_SYSTEM_CERTIFICATES_DEFAULT
// debugging settings
val prefLogToExternalStorage = findPreference(Logger.LOG_TO_EXTERNAL_STORAGE) as SwitchPreferenceCompat
prefLogToExternalStorage.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val context = activity!!
Logger.initialize(context)
// kill a potential :sync process, so that the new logger settings will be used
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
am.runningAppProcesses.forEach {
if (it.pid != Process.myPid()) {
Logger.log.info("Killing ${it.processName} process, pid = ${it.pid}")
Process.killProcess(it.pid)
}
}
true
}
}
override fun onSettingsChanged() {
loadSettings()
}
private fun resetHints() {
settings?.remove(StartupDialogFragment.HINT_AUTOSTART_PERMISSIONS)
settings?.remove(StartupDialogFragment.HINT_BATTERY_OPTIMIZATIONS)
settings?.remove(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED)
settings?.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED)
val settings = Settings.getInstance(requireActivity())
settings.remove(StartupDialogFragment.HINT_AUTOSTART_PERMISSIONS)
settings.remove(StartupDialogFragment.HINT_BATTERY_OPTIMIZATIONS)
settings.remove(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED)
settings.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED)
Snackbar.make(view!!, R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show()
}

View file

@ -11,8 +11,8 @@ package at.bitfire.davdroid.ui
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import at.bitfire.davdroid.R
import at.bitfire.davdroid.model.CollectionInfo
import kotlinx.android.synthetic.main.collection_properties.view.*

View file

@ -13,11 +13,11 @@ import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.core.app.NavUtils
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NavUtils
import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
@ -29,8 +29,8 @@ import kotlinx.android.synthetic.main.activity_create_calendar.*
import net.fortuna.ical4j.model.Calendar
import okhttp3.HttpUrl
import org.apache.commons.lang3.StringUtils
import yuku.ambilwarna.AmbilWarnaDialog
import java.util.*
import yuku.ambilwarna.AmbilWarnaDialog
class CreateCalendarActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<CreateCalendarActivity.AccountInfo> {

View file

@ -19,14 +19,13 @@ import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import at.bitfire.dav4android.DavResource
import at.bitfire.dav4android.XmlUtils
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.AccountSettings
import java.io.IOException
import java.io.StringWriter
import java.util.logging.Level
@ -184,42 +183,40 @@ class CreateCollectionFragment: DialogFragment(), LoaderManager.LoaderCallbacks<
Logger.log.log(Level.SEVERE, "Couldn't assemble Extended MKCOL request", e)
}
Settings.getInstance(context)?.use { settings ->
HttpClient.Builder(context, settings, AccountSettings(context, settings, account))
.setForeground(true)
.build().use { httpClient ->
try {
val collection = DavResource(httpClient.okHttpClient, info.url)
HttpClient.Builder(context, AccountSettings(context, account))
.setForeground(true)
.build().use { httpClient ->
try {
val collection = DavResource(httpClient.okHttpClient, info.url)
// create collection on remote server
collection.mkCol(writer.toString()) {}
// create collection on remote server
collection.mkCol(writer.toString()) {}
// now insert collection into database:
ServiceDB.OpenHelper(context).use { dbHelper ->
val db = dbHelper.writableDatabase
// now insert collection into database:
ServiceDB.OpenHelper(context).use { dbHelper ->
val db = dbHelper.writableDatabase
// 1. find service ID
val serviceType = when (info.type) {
CollectionInfo.Type.ADDRESS_BOOK -> ServiceDB.Services.SERVICE_CARDDAV
CollectionInfo.Type.CALENDAR -> ServiceDB.Services.SERVICE_CALDAV
else -> throw IllegalArgumentException("Collection must be an address book or calendar")
}
db.query(ServiceDB.Services._TABLE, arrayOf(ServiceDB.Services.ID),
"${ServiceDB.Services.ACCOUNT_NAME}=? AND ${ServiceDB.Services.SERVICE}=?",
arrayOf(account.name, serviceType), null, null, null).use { c ->
assert(c.moveToNext())
val serviceID = c.getLong(0)
// 2. add collection to service
val values = info.toDB()
values.put(ServiceDB.Collections.SERVICE_ID, serviceID)
db.insert(ServiceDB.Collections._TABLE, null, values)
}
// 1. find service ID
val serviceType = when (info.type) {
CollectionInfo.Type.ADDRESS_BOOK -> ServiceDB.Services.SERVICE_CARDDAV
CollectionInfo.Type.CALENDAR -> ServiceDB.Services.SERVICE_CALDAV
else -> throw IllegalArgumentException("Collection must be an address book or calendar")
}
db.query(ServiceDB.Services._TABLE, arrayOf(ServiceDB.Services.ID),
"${ServiceDB.Services.ACCOUNT_NAME}=? AND ${ServiceDB.Services.SERVICE}=?",
arrayOf(account.name, serviceType), null, null, null).use { c ->
assert(c.moveToNext())
val serviceID = c.getLong(0)
// 2. add collection to service
val values = info.toDB()
values.put(ServiceDB.Collections.SERVICE_ID, serviceID)
db.insert(ServiceDB.Collections._TABLE, null, values)
}
} catch(e: Exception) {
return e
}
} catch(e: Exception) {
return e
}
}
return null

View file

@ -34,14 +34,13 @@ import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import at.bitfire.dav4android.exception.HttpException
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.TaskProvider
import kotlinx.android.synthetic.main.activity_debug_info.*
import org.dmfs.tasks.contract.TaskContract
@ -260,27 +259,24 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
.append("\n")
// main accounts
val accountManager = AccountManager.get(context)
Settings.getInstance(context)?.let { settings ->
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type))) {
try {
val accountSettings = AccountSettings(context, settings, acct)
report.append("Account: ${acct.name}\n" +
" Address book sync. interval: ${syncStatus(accountSettings, context.getString(R.string.address_books_authority))}\n" +
" Calendar sync. interval: ${syncStatus(accountSettings, CalendarContract.AUTHORITY)}\n" +
" OpenTasks sync. interval: ${syncStatus(accountSettings, TaskProvider.ProviderName.OpenTasks.authority)}\n" +
" WiFi only: ").append(accountSettings.getSyncWifiOnly())
accountSettings.getSyncWifiOnlySSIDs()?.let {
report.append(", SSIDs: ${accountSettings.getSyncWifiOnlySSIDs()}")
}
report.append("\n [CardDAV] Contact group method: ${accountSettings.getGroupMethod()}")
.append("\n [CalDAV] Time range (past days): ${accountSettings.getTimeRangePastDays()}")
.append("\n Manage calendar colors: ${accountSettings.getManageCalendarColors()}")
.append("\n")
} catch (e: InvalidAccountException) {
report.append("$acct is invalid (unsupported settings version) or does not exist\n")
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
try {
val accountSettings = AccountSettings(context, acct)
report.append("Account: ${acct.name}\n" +
" Address book sync. interval: ${syncStatus(accountSettings, context.getString(R.string.address_books_authority))}\n" +
" Calendar sync. interval: ${syncStatus(accountSettings, CalendarContract.AUTHORITY)}\n" +
" OpenTasks sync. interval: ${syncStatus(accountSettings, TaskProvider.ProviderName.OpenTasks.authority)}\n" +
" WiFi only: ").append(accountSettings.getSyncWifiOnly())
accountSettings.getSyncWifiOnlySSIDs()?.let {
report.append(", SSIDs: ${accountSettings.getSyncWifiOnlySSIDs()}")
}
report.append("\n [CardDAV] Contact group method: ${accountSettings.getGroupMethod()}")
.append("\n [CalDAV] Time range (past days): ${accountSettings.getTimeRangePastDays()}")
.append("\n Manage calendar colors: ${accountSettings.getManageCalendarColors()}")
.append("\n")
} catch (e: InvalidAccountException) {
report.append("$acct is invalid (unsupported settings version) or does not exist\n")
}
}
// address book accounts
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)))
try {

View file

@ -9,6 +9,7 @@
package at.bitfire.davdroid.ui
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.Menu
@ -16,7 +17,6 @@ import android.view.MenuItem
import at.bitfire.davdroid.App
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import at.bitfire.davdroid.settings.ISettings
class DefaultAccountsDrawerHandler: IAccountsDrawerHandler {
@ -25,7 +25,7 @@ class DefaultAccountsDrawerHandler: IAccountsDrawerHandler {
}
override fun onSettingsChanged(settings: ISettings?, menu: Menu) {
override fun initMenu(context: Context, menu: Menu) {
if (BuildConfig.VERSION_NAME.contains("-beta") || BuildConfig.VERSION_NAME.contains("-rc"))
menu.findItem(R.id.nav_beta_feedback).isVisible = true
}

View file

@ -19,12 +19,11 @@ import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import at.bitfire.dav4android.DavResource
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.R
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.AccountSettings
class DeleteCollectionFragment: DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {
@ -83,24 +82,22 @@ class DeleteCollectionFragment: DialogFragment(), LoaderManager.LoaderCallbacks<
override fun onStartLoading() = forceLoad()
override fun loadInBackground(): Exception? {
Settings.getInstance(context)?.use { settings ->
HttpClient.Builder(context, settings, AccountSettings(context, settings, account))
.setForeground(true)
.build().use { httpClient ->
try {
val collection = DavResource(httpClient.okHttpClient, collectionInfo.url)
HttpClient.Builder(context, AccountSettings(context, account))
.setForeground(true)
.build().use { httpClient ->
try {
val collection = DavResource(httpClient.okHttpClient, collectionInfo.url)
// delete collection from server
collection.delete(null) {}
// delete collection from server
collection.delete(null) {}
// delete collection locally
ServiceDB.OpenHelper(context).use { dbHelper ->
val db = dbHelper.writableDatabase
db.delete(ServiceDB.Collections._TABLE, "${ServiceDB.Collections.ID}=?", arrayOf(collectionInfo.id.toString()))
}
} catch(e: Exception) {
return e
// delete collection locally
ServiceDB.OpenHelper(context).use { dbHelper ->
val db = dbHelper.writableDatabase
db.delete(ServiceDB.Collections._TABLE, "${ServiceDB.Collections.ID}=?", arrayOf(collectionInfo.id.toString()))
}
} catch(e: Exception) {
return e
}
}
return null

View file

@ -12,8 +12,8 @@ import android.accounts.Account
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import at.bitfire.dav4android.exception.HttpException
import at.bitfire.davdroid.R
import java.io.IOException

View file

@ -9,13 +9,13 @@
package at.bitfire.davdroid.ui
import android.app.Activity
import android.content.Context
import android.view.Menu
import android.view.MenuItem
import at.bitfire.davdroid.settings.ISettings
interface IAccountsDrawerHandler {
fun onSettingsChanged(settings: ISettings?, menu: Menu)
fun initMenu(context: Context, menu: Menu)
fun onNavigationItemSelected(activity: Activity, item: MenuItem): Boolean

View file

@ -1,66 +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 at.bitfire.davdroid.ui
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Handler
import android.os.IBinder
import androidx.loader.content.AsyncTaskLoader
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.ISettingsObserver
import at.bitfire.davdroid.settings.Settings
abstract class SettingsLoader<T>(
context: Context
): AsyncTaskLoader<T>(context) {
val handler = Handler()
val settingsObserver = object: ISettingsObserver.Stub() {
override fun onSettingsChanged() {
handler.post {
onContentChanged()
}
}
}
private var settingsSvc: ServiceConnection? = null
var settings: ISettings? = null
override fun onStartLoading() {
if (settingsSvc != null)
forceLoad()
else {
val serviceConn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
settings = ISettings.Stub.asInterface(binder)
settings!!.registerObserver(settingsObserver)
onContentChanged()
}
override fun onServiceDisconnected(name: ComponentName?) {
settings!!.unregisterObserver(settingsObserver)
settings = null
}
}
if (context.bindService(Intent(context, Settings::class.java), serviceConn, Context.BIND_AUTO_CREATE))
settingsSvc = serviceConn
}
}
override fun onReset() {
settingsSvc?.let {
context.unbindService(it)
settingsSvc = null
}
}
}

View file

@ -21,18 +21,16 @@ import android.os.Bundle
import android.os.PowerManager
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import at.bitfire.davdroid.App
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.settings.Settings
import java.util.*
import java.util.logging.Level
class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISettings> {
class StartupDialogFragment: DialogFragment() {
enum class Mode {
AUTOSTART_PERMISSIONS,
@ -56,33 +54,34 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
const val ARGS_MODE = "mode"
fun getStartupDialogs(context: Context, settings: ISettings): List<StartupDialogFragment> {
fun getStartupDialogs(context: Context): List<StartupDialogFragment> {
val dialogs = LinkedList<StartupDialogFragment>()
val settings = Settings.getInstance(context)
if (System.currentTimeMillis() > settings.getLong(SETTING_NEXT_DONATION_POPUP, 0))
if (System.currentTimeMillis() > settings.getLong(SETTING_NEXT_DONATION_POPUP) ?: 0)
dialogs += StartupDialogFragment.instantiate(Mode.OSE_DONATE)
// store-specific information
/*if (BuildConfig.FLAVOR == App.FLAVOR_GOOGLE_PLAY) {
// Play store
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && // only on Android <5
settings.getBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, true)) // and only when "Don't show again" hasn't been clicked yet
settings.getBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED) != false) // and only when "Don't show again" hasn't been clicked yet
dialogs += StartupDialogFragment.instantiate(Mode.GOOGLE_PLAY_ACCOUNTS_REMOVED)
}*/
// battery optimization white-listing
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS, true)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS) != false) {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
if (!powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
dialogs.add(StartupDialogFragment.instantiate(Mode.BATTERY_OPTIMIZATIONS))
}
// vendor-specific auto-start information
if (autostartManufacturers.contains(Build.MANUFACTURER.toLowerCase()) && settings.getBoolean(HINT_AUTOSTART_PERMISSIONS, true))
if (autostartManufacturers.contains(Build.MANUFACTURER.toLowerCase()) && settings.getBoolean(HINT_AUTOSTART_PERMISSIONS) != false)
dialogs.add(StartupDialogFragment.instantiate(Mode.AUTOSTART_PERMISSIONS))
// OpenTasks information
if (!LocalTaskList.tasksProviderAvailable(context) && settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED, true))
if (!LocalTaskList.tasksProviderAvailable(context) && settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED) != false)
dialogs.add(StartupDialogFragment.instantiate(Mode.OPENTASKS_NOT_INSTALLED))
return dialogs.reversed()
@ -98,30 +97,12 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
}
var settings: ISettings? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateLoader(code: Int, args: Bundle?) =
SettingsLoader(requireActivity())
override fun onLoadFinished(loader: Loader<ISettings>, result: ISettings?) {
settings = result
}
override fun onLoaderReset(loader: Loader<ISettings>) {
settings = null
}
@SuppressLint("BatteryLife")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
isCancelable = false
val settings = Settings.getInstance(requireActivity())
val activity = requireActivity()
val mode = Mode.valueOf(arguments!!.getString(ARGS_MODE)!!)
return when (mode) {
@ -136,7 +117,7 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
}
.setNeutralButton(R.string.startup_not_now) { _, _ -> }
.setNegativeButton(R.string.startup_dont_show_again) { _, _ ->
settings?.putBoolean(HINT_AUTOSTART_PERMISSIONS, false)
settings.putBoolean(HINT_AUTOSTART_PERMISSIONS, false)
}
.create()
@ -151,7 +132,7 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
}
.setNeutralButton(R.string.startup_not_now) { _, _ -> }
.setNegativeButton(R.string.startup_dont_show_again) { _: DialogInterface, _: Int ->
settings?.putBoolean(HINT_BATTERY_OPTIMIZATIONS, false)
settings.putBoolean(HINT_BATTERY_OPTIMIZATIONS, false)
}
.create()
@ -172,7 +153,7 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
}
.setNeutralButton(R.string.startup_not_now) { _, _ -> }
.setNegativeButton(R.string.startup_dont_show_again) { _, _ ->
settings?.putBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, false)
settings.putBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, false)
}
.create()
}
@ -191,7 +172,7 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
}
.setNeutralButton(R.string.startup_not_now) { _, _ -> }
.setNegativeButton(R.string.startup_dont_show_again) { _: DialogInterface, _: Int ->
settings?.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false)
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false)
}
.create()
}
@ -205,26 +186,14 @@ class StartupDialogFragment: DialogFragment(), LoaderManager.LoaderCallbacks<ISe
UiUtils.launchUri(requireActivity(), App.homepageUrl(requireActivity()).buildUpon()
.appendEncodedPath("donate/")
.build())
settings?.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 30 * 86400000L) // 30 days
settings.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 30 * 86400000L) // 30 days
}
.setNegativeButton(R.string.startup_donate_later) { _, _ ->
settings?.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 14 * 86400000L) // 14 days
settings.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 14 * 86400000L) // 14 days
}
.create()
}
}
class SettingsLoader(
context: Context
): at.bitfire.davdroid.ui.SettingsLoader<ISettings?>(context) {
override fun loadInBackground(): ISettings? {
settings?.let { return it }
return null
}
}
}

View file

@ -24,15 +24,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import at.bitfire.davdroid.*
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.DavService
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.ServiceDB.*
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.ISettings
import at.bitfire.davdroid.ui.SettingsLoader
import at.bitfire.davdroid.ui.setup.AccountDetailsFragment.CreateSettings
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.ical4android.TaskProvider
import at.bitfire.vcard4android.GroupMethod
import com.google.android.material.snackbar.Snackbar
@ -41,7 +41,7 @@ import kotlinx.android.synthetic.main.login_account_details.view.*
import java.lang.ref.WeakReference
import java.util.logging.Level
class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSettings> {
class AccountDetailsFragment: Fragment() {
companion object {
const val KEY_CONFIG = "config"
@ -55,9 +55,6 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
}
}
var groupMethod: GroupMethod? = null
var settings: ISettings? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val v = inflater.inflate(R.layout.login_account_details, container, false)
@ -73,72 +70,51 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
config.credentials.userName ?:
config.credentials.certificateAlias)
val settings = Settings.getInstance(requireActivity())
// CardDAV-specific
v.carddav.visibility = if (config.cardDAV != null) View.VISIBLE else View.GONE
settings?.let {
if (it.has(AccountSettings.KEY_CONTACT_GROUP_METHOD))
v.contact_group_method.isEnabled = false
}
if (settings.has(AccountSettings.KEY_CONTACT_GROUP_METHOD))
v.contact_group_method.isEnabled = false
v.create_account.setOnClickListener { _ ->
val name = v.account_name.text.toString()
if (name.isEmpty())
v.account_name.error = getString(R.string.login_account_name_required)
else
settings?.let {
val idx = view!!.contact_group_method.selectedItemPosition
val groupMethodName = resources.getStringArray(R.array.settings_contact_group_method_values)[idx]
else {
val idx = view!!.contact_group_method.selectedItemPosition
val groupMethodName = resources.getStringArray(R.array.settings_contact_group_method_values)[idx]
v.create_account.visibility = View.GONE
v.create_account_progress.visibility = View.VISIBLE
v.create_account.visibility = View.GONE
v.create_account_progress.visibility = View.VISIBLE
CreateAccountTask(requireActivity().applicationContext, WeakReference(requireActivity()),
it, name,
args.getParcelable(KEY_CONFIG) as DavResourceFinder.Configuration,
GroupMethod.valueOf(groupMethodName)).execute()
}
CreateAccountTask(requireActivity().applicationContext, WeakReference(requireActivity()),
name,
args.getParcelable(KEY_CONFIG) as DavResourceFinder.Configuration,
GroupMethod.valueOf(groupMethodName)).execute()
}
}
LoaderManager.getInstance(this).initLoader(0, null, this)
val forcedGroupMethod = settings.getString(AccountSettings.KEY_CONTACT_GROUP_METHOD)?.let { GroupMethod.valueOf(it) }
if (forcedGroupMethod != null) {
v.contact_group_method.isEnabled = false
for ((i, method) in resources.getStringArray(R.array.settings_contact_group_method_values).withIndex()) {
if (method == forcedGroupMethod.name) {
v.contact_group_method.setSelection(i)
break
}
}
} else
v.contact_group_method.isEnabled = true
return v
}
override fun onCreateLoader(code: Int, args: Bundle?) =
GroupMethodLoader(requireActivity())
override fun onLoadFinished(loader: Loader<CreateSettings>, result: CreateSettings?) {
settings = (result ?: return).settings
groupMethod = result.groupMethod
view?.let { view ->
if (result.groupMethod != null) {
view.contact_group_method.isEnabled = false
for ((i, method) in resources.getStringArray(R.array.settings_contact_group_method_values).withIndex()) {
if (method == result.groupMethod.name) {
view.contact_group_method.setSelection(i)
break
}
}
} else
view.contact_group_method.isEnabled = true
view.create_account.isEnabled = true
}
}
override fun onLoaderReset(loader: Loader<CreateSettings>) {
settings = null
groupMethod = null
view?.create_account?.isEnabled = false
}
@SuppressLint("StaticFieldLeak") // we'll only keep the application Context
class CreateAccountTask(
private val applicationContext: Context,
private val appContext: Context,
private val activityRef: WeakReference<Activity>,
private val settings: ISettings,
private val accountName: String,
private val config: DavResourceFinder.Configuration,
@ -146,24 +122,24 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
): AsyncTask<Void, Void, Boolean>() {
override fun doInBackground(vararg params: Void?): Boolean {
val account = Account(accountName, applicationContext.getString(R.string.account_type))
val account = Account(accountName, appContext.getString(R.string.account_type))
// create Android account
val userData = AccountSettings.initialUserData(config.credentials)
Logger.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, userData))
val accountManager = AccountManager.get(applicationContext)
val accountManager = AccountManager.get(appContext)
if (!accountManager.addAccountExplicitly(account, config.credentials.password, userData))
return false
// add entries for account to service DB
Logger.log.log(Level.INFO, "Writing account configuration to database", config)
OpenHelper(applicationContext).use { dbHelper ->
OpenHelper(appContext).use { dbHelper ->
val db = dbHelper.writableDatabase
try {
val accountSettings = AccountSettings(applicationContext, settings, account)
val accountSettings = AccountSettings(appContext, account)
val refreshIntent = Intent(applicationContext, DavService::class.java)
val refreshIntent = Intent(appContext, DavService::class.java)
refreshIntent.action = DavService.ACTION_REFRESH_COLLECTIONS
if (config.cardDAV != null) {
@ -172,15 +148,15 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
// start CardDAV service detection (refresh collections)
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id)
applicationContext.startService(refreshIntent)
appContext.startService(refreshIntent)
// initial CardDAV account settings
accountSettings.setGroupMethod(groupMethod)
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_address_books.xml
accountSettings.setSyncInterval(applicationContext.getString(R.string.address_books_authority), Constants.DEFAULT_SYNC_INTERVAL)
accountSettings.setSyncInterval(appContext.getString(R.string.address_books_authority), Constants.DEFAULT_SYNC_INTERVAL)
} else
ContentResolver.setIsSyncable(account, applicationContext.getString(R.string.address_books_authority), 0)
ContentResolver.setIsSyncable(account, appContext.getString(R.string.address_books_authority), 0)
if (config.calDAV != null) {
// insert CalDAV service
@ -188,14 +164,14 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
// start CalDAV service detection (refresh collections)
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id)
applicationContext.startService(refreshIntent)
appContext.startService(refreshIntent)
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_calendars.xml
accountSettings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL)
// enable task sync if OpenTasks is installed
// further changes will be handled by PackageChangedReceiver
if (LocalTaskList.tasksProviderAvailable(applicationContext)) {
if (LocalTaskList.tasksProviderAvailable(appContext)) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1)
accountSettings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL)
}
@ -254,34 +230,4 @@ class AccountDetailsFragment: Fragment(), LoaderManager.LoaderCallbacks<CreateSe
}
data class CreateSettings(
val settings: ISettings,
val groupMethod: GroupMethod?
)
class GroupMethodLoader(
context: Context
): SettingsLoader<CreateSettings>(context) {
override fun loadInBackground(): CreateSettings? {
settings?.let { settings ->
var groupMethod: GroupMethod? = null
settings.getString(AccountSettings.KEY_CONTACT_GROUP_METHOD, null)?.let {
try {
groupMethod = GroupMethod.valueOf(it)
} catch (e: IllegalArgumentException) {
}
}
return CreateSettings(
settings,
groupMethod
)
}
return null
}
}
}
}

View file

@ -24,7 +24,6 @@ import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.log.StringHandler
import at.bitfire.davdroid.model.CollectionInfo
import at.bitfire.davdroid.model.Credentials
import at.bitfire.davdroid.settings.Settings
import okhttp3.HttpUrl
import org.apache.commons.lang3.builder.ReflectionToStringBuilder
import org.xbill.DNS.Lookup
@ -57,14 +56,12 @@ class DavResourceFinder(
log.addHandler(logBuffer)
}
private val settings = Settings.getInstance(context)
private val httpClient: HttpClient = HttpClient.Builder(context, settings, logger = log)
private val httpClient: HttpClient = HttpClient.Builder(context, logger = log)
.addAuthentication(null, loginInfo.credentials)
.setForeground(true)
.build()
override fun close() {
settings?.close()
httpClient.close()
}

View file

@ -9,9 +9,9 @@
package at.bitfire.davdroid.ui.setup
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import at.bitfire.davdroid.App
import at.bitfire.davdroid.R
import at.bitfire.davdroid.ui.UiUtils

View file

@ -9,8 +9,8 @@
package at.bitfire.davdroid.ui.widget
import android.content.Context
import androidx.preference.EditTextPreference
import android.util.AttributeSet
import androidx.preference.EditTextPreference
class IntEditTextPreference(
context: Context,

View file

@ -85,7 +85,6 @@
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/login_create_account"
android:enabled="false"
style="@style/stepper_nav_button"/>
<ProgressBar

View file

@ -0,0 +1,2 @@
at.bitfire.davdroid.settings.DefaultsProvider$Factory
at.bitfire.davdroid.settings.SharedPreferencesProvider$Factory

@ -1 +1 @@
Subproject commit 2c7523be31985e683cedd342c48829dbee096c67
Subproject commit f2078bc846f1b704680904c415e95ce8a9c9f672

@ -1 +1 @@
Subproject commit b63883b21c0680b3b41bc4d067ca2985c58a15c8
Subproject commit 42d5cc3f8b16c628fa13a5a3b0f211e6660fb084