Commit 54cc3bec authored by Ricki Hirner's avatar Ricki Hirner 🐑

Refactor Loaders, HttpClient

* improve Loader implementation
* use one shared HttpClient singleton
parent d4324bcf
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid;
package at.bitfire.davdroid
import android.content.Context
import android.os.Build
......@@ -35,6 +35,23 @@ class HttpClient private constructor(
private val certManager: CustomCertManager?
): Closeable {
companion object {
/** [OkHttpClient] singleton to build all clients from */
val sharedClient = OkHttpClient.Builder()
// set timeouts
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
// don't allow redirects by default, because it would break PROPFIND handling
.followRedirects(false)
// add User-Agent to every request
.addNetworkInterceptor(UserAgentInterceptor)
.build()!!
}
override fun close() {
certManager?.close()
}
......@@ -45,21 +62,10 @@ class HttpClient private constructor(
accountSettings: AccountSettings? = null,
logger: java.util.logging.Logger = Logger.log
) {
var certManager: CustomCertManager? = null
private val orig = OkHttpClient.Builder()
private var certManager: CustomCertManager? = null
private val orig = sharedClient.newBuilder()
init {
// set timeouts
orig.connectTimeout(30, TimeUnit.SECONDS)
orig.writeTimeout(30, TimeUnit.SECONDS)
orig.readTimeout(120, TimeUnit.SECONDS)
// don't allow redirects by default, because it would break PROPFIND handling
orig.followRedirects(false)
// add User-Agent to every request
orig.addNetworkInterceptor(UserAgentInterceptor)
// add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
orig.cookieJar(MemoryCookieStore())
......
......@@ -186,13 +186,13 @@ class AboutActivity: AppCompatActivity() {
val fileName: String
): AsyncTaskLoader<Spanned>(context) {
var content: Spanned? = null
private var content: Spanned? = null
override fun onStartLoading() {
if (content == null)
forceLoad()
else
if (content != null)
deliverResult(content)
else
forceLoad()
}
override fun loadInBackground(): Spanned? {
......
......@@ -240,7 +240,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
}
R.id.delete_collection ->
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
}
true
})
......@@ -363,6 +363,33 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
else
View.GONE
} ?: View.GONE
// ask for permissions
val requiredPermissions = mutableSetOf<String>()
info?.carddav?.let { carddav ->
if (carddav.collections.any { it.type == CollectionInfo.Type.ADDRESS_BOOK }) {
requiredPermissions += Manifest.permission.READ_CONTACTS
requiredPermissions += Manifest.permission.WRITE_CONTACTS
}
}
info?.caldav?.let { caldav ->
if (caldav.collections.any { it.type == CollectionInfo.Type.CALENDAR }) {
requiredPermissions += Manifest.permission.READ_CALENDAR
requiredPermissions += Manifest.permission.WRITE_CALENDAR
if (LocalTaskList.tasksProviderAvailable(this)) {
requiredPermissions += TaskProvider.PERMISSION_READ_TASKS
requiredPermissions += TaskProvider.PERMISSION_WRITE_TASKS
}
}
if (caldav.collections.any { it.type == CollectionInfo.Type.WEBCAL })
requiredPermissions += Manifest.permission.READ_CALENDAR
}
val askPermissions = requiredPermissions.filter { ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
if (askPermissions.isNotEmpty())
ActivityCompat.requestPermissions(this, askPermissions.toTypedArray(), 0)
}
override fun onLoaderReset(loader: Loader<AccountInfo>) {
......@@ -372,44 +399,58 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
class AccountLoader(
val activity: AccountActivity,
context: Context,
val account: Account
): AsyncTaskLoader<AccountInfo>(activity), DavService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
): AsyncTaskLoader<AccountInfo>(context), DavService.RefreshingStatusListener, SyncStatusObserver {
private var syncStatusListener: Any? = null
private var davServiceConn: ServiceConnection? = null
private var davService: DavService.InfoBinder? = null
private lateinit var syncStatusListener: Any
override fun onStartLoading() {
syncStatusListener = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, this)
context.bindService(Intent(context, DavService::class.java), this, Context.BIND_AUTO_CREATE)
}
// get notified when sync status changes
if (syncStatusListener == null)
syncStatusListener = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, this)
// bind to DavService to get notified when it's running
if (davServiceConn == null) {
davServiceConn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
// get notified when DavService is running
davService = service as DavService.InfoBinder
service.addRefreshingStatusListener(this@AccountLoader, false)
onContentChanged()
}
override fun onStopLoading() {
davService?.removeRefreshingStatusListener(this)
context.unbindService(this)
ContentResolver.removeStatusChangeListener(syncStatusListener)
override fun onServiceDisconnected(name: ComponentName) {
davService = null
}
}
context.bindService(Intent(context, DavService::class.java), davServiceConn, Context.BIND_AUTO_CREATE)
} else
forceLoad()
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
davService = service as DavService.InfoBinder
service.addRefreshingStatusListener(this, false)
forceLoad()
}
override fun onReset() {
ContentResolver.removeStatusChangeListener(syncStatusListener)
override fun onServiceDisconnected(name: ComponentName) {
davService = null
davService?.removeRefreshingStatusListener(this)
davServiceConn?.let {
context.unbindService(it)
davServiceConn = null
}
}
override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) =
forceLoad()
onContentChanged()
override fun onStatusChanged(which: Int) =
forceLoad()
onContentChanged()
override fun loadInBackground(): AccountInfo {
val info = AccountInfo()
val requiredPermissions = mutableSetOf<String>()
OpenHelper(context).use { dbHelper ->
val db = dbHelper.readableDatabase
......@@ -440,7 +481,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
carddav.hasHomeSets = hasHomeSets(db, id)
carddav.collections = readCollections(db, id, requiredPermissions)
carddav.collections = readCollections(db, id)
}
Services.SERVICE_CALDAV -> {
val caldav = AccountInfo.ServiceInfo()
......@@ -451,17 +492,13 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) ||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority)
caldav.hasHomeSets = hasHomeSets(db, id)
caldav.collections = readCollections(db, id, requiredPermissions)
caldav.collections = readCollections(db, id)
}
}
}
}
}
val askPermissions = requiredPermissions.filter { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED }
if (askPermissions.isNotEmpty())
ActivityCompat.requestPermissions(activity, askPermissions.toTypedArray(), 0)
return info
}
......@@ -473,7 +510,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
return false
}
private fun readCollections(db: SQLiteDatabase, service: Long, requiredPermissions: MutableSet<String>): List<CollectionInfo> {
private fun readCollections(db: SQLiteDatabase, service: Long): List<CollectionInfo> {
val collections = LinkedList<CollectionInfo>()
db.query(Collections._TABLE, null, Collections.SERVICE_ID + "=?", arrayOf(service.toString()),
null, null, "${Collections.SUPPORTS_VEVENT} DESC,${Collections.DISPLAY_NAME}").use { cursor ->
......@@ -502,19 +539,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
}
if (collections.any { it.type == CollectionInfo.Type.ADDRESS_BOOK } && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions += Manifest.permission.READ_CONTACTS
requiredPermissions += Manifest.permission.WRITE_CONTACTS
}
if (collections.any { it.type == CollectionInfo.Type.CALENDAR }) {
requiredPermissions += Manifest.permission.READ_CALENDAR
requiredPermissions += Manifest.permission.WRITE_CALENDAR
requiredPermissions += TaskProvider.PERMISSION_READ_TASKS
requiredPermissions += TaskProvider.PERMISSION_WRITE_TASKS
}
if (collections.any { it.type == CollectionInfo.Type.WEBCAL })
requiredPermissions += Manifest.permission.READ_CALENDAR
return collections
}
......@@ -640,7 +664,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
.setTitle(R.string.account_rename)
.setMessage(R.string.account_rename_new_name)
.setView(editText)
.setPositiveButton(R.string.account_rename_rename, DialogInterface.OnClickListener { dialog, which ->
.setPositiveButton(R.string.account_rename_rename, DialogInterface.OnClickListener { _, _ ->
val newName = editText.text.toString()
if (newName == oldAccount.name)
......
......@@ -66,18 +66,26 @@ class AccountListFragment: ListFragment(), LoaderManager.LoaderCallbacks<Array<A
class AccountLoader(
context: Context
): AsyncTaskLoader<Array<Account>>(context), OnAccountsUpdateListener {
): AsyncTaskLoader<Array<Account>>(context) {
val accountManager = AccountManager.get(context)!!
private val accountManager = AccountManager.get(context)!!
private var listener: OnAccountsUpdateListener? = null
override fun onStartLoading() =
accountManager.addOnAccountsUpdatedListener(this, null, true)
override fun onStartLoading() {
if (listener == null) {
listener = OnAccountsUpdateListener { onContentChanged() }
accountManager.addOnAccountsUpdatedListener(listener, null, false)
}
override fun onStopLoading() =
accountManager.removeOnAccountsUpdatedListener(this)
forceLoad()
}
override fun onAccountsUpdated(accounts: Array<Account>?) =
forceLoad()
override fun onReset() {
listener?.let {
accountManager.removeOnAccountsUpdatedListener(it)
listener = null
}
}
override fun loadInBackground(): Array<Account> =
AccountManager.get(context).getAccountsByType(context.getString(R.string.account_type))
......
......@@ -293,17 +293,22 @@ class AccountSettingsActivity: AppCompatActivity() {
val account: Account
): SettingsLoader<Pair<ISettings, AccountSettings>?>(context), SyncStatusObserver {
var listenerHandle: Any? = null
private var listenerHandle: Any? = null
override fun onStartLoading() {
super.onStartLoading()
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this@AccountSettingsLoader)
if (listenerHandle == null)
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this@AccountSettingsLoader)
}
override fun onStopLoading() {
super.onStopLoading()
listenerHandle?.let { ContentResolver.removeStatusChangeListener(it) }
listenerHandle = null
override fun onReset() {
super.onReset()
listenerHandle?.let {
ContentResolver.removeStatusChangeListener(it)
listenerHandle = null
}
}
override fun loadInBackground(): Pair<ISettings, AccountSettings>? {
......@@ -320,7 +325,7 @@ class AccountSettingsActivity: AppCompatActivity() {
}
override fun onStatusChanged(which: Int) {
forceLoad()
onContentChanged()
}
}
......
......@@ -120,9 +120,17 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
val extras: Bundle?
): AsyncTaskLoader<String>(context) {
override fun onStartLoading() = forceLoad()
var result: String? = null
override fun onStartLoading() {
if (result != null)
deliverResult(result)
else
forceLoad()
}
override fun loadInBackground(): String {
Logger.log.info("Building debug info")
val report = StringBuilder("--- BEGIN DEBUG INFO ---\n")
// begin with most specific information
......@@ -270,7 +278,11 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
}
report.append("--- END DEBUG INFO ---\n")
return report.toString()
report.toString().let {
result = it
return it
}
}
private fun syncStatus(settings: AccountSettings, authority: String): String {
......
......@@ -23,33 +23,39 @@ abstract class SettingsLoader<T>(
val settingsObserver = object: ISettingsObserver.Stub() {
override fun onSettingsChanged() {
handler.post {
forceLoad()
onContentChanged()
}
}
}
private var settingsSvc: ServiceConnection? = null
var settings: ISettings? = null
private val settingsSvc = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
settings = ISettings.Stub.asInterface(binder)
settings!!.registerObserver(settingsObserver)
override fun onStartLoading() {
if (settingsSvc != null)
forceLoad()
else {
settingsSvc = 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
}
}
context.bindService(Intent(context, Settings::class.java), settingsSvc, Context.BIND_AUTO_CREATE)
}
override fun onServiceDisconnected(name: ComponentName?) {
settings!!.unregisterObserver(settingsObserver)
settings = null
}
}
override fun onStartLoading() {
context.bindService(Intent(context, Settings::class.java), settingsSvc, Context.BIND_AUTO_CREATE)
}
override fun onStopLoading() {
context.unbindService(settingsSvc)
override fun onReset() {
settingsSvc?.let {
context.unbindService(it)
settingsSvc = null
}
}
}
\ No newline at end of file
......@@ -59,7 +59,7 @@ class DavResourceFinder(
fun cancel() {
log.warning("Shutting down resource detection")
httpClient.okHttpClient.dispatcher().executorService().shutdownNow()
httpClient.okHttpClient.dispatcher().executorService().shutdown()
httpClient.okHttpClient.connectionPool().evictAll()
}
......
......@@ -111,18 +111,18 @@ class DetectConfigurationFragment: DialogFragment(), LoaderManager.LoaderCallbac
class ServerConfigurationLoader(
context: Context,
val credentials: LoginCredentials
private val credentials: LoginCredentials
): AsyncTaskLoader<Configuration>(context) {
var resourceFinder: DavResourceFinder? = null
override fun onStartLoading() = forceLoad()
override fun onCancelLoad(): Boolean {
override fun cancelLoadInBackground() {
thread {
resourceFinder?.cancel()
resourceFinder = null
}
return true
}
override fun loadInBackground(): Configuration {
......
......@@ -9,7 +9,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.dokka_version = '0.9.15'
ext.kotlin_version = '1.2.0'
ext.kotlin_version = '1.2.10'
repositories {
jcenter()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment