mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-10-14 15:29:16 +00:00
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:
parent
b863d355f6
commit
7d4689969a
185
Settings.kt
Normal file
185
Settings.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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"/>
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package at.bitfire.davdroid.settings;
|
||||
|
||||
interface ISettingsObserver {
|
||||
|
||||
void onSettingsChanged();
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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.*
|
||||
|
|
|
@ -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> {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue