/* * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package com.etesync.syncadapter import android.accounts.Account import android.accounts.AccountManager import android.annotation.SuppressLint import android.annotation.TargetApi import android.content.BroadcastReceiver import android.content.ContentResolver import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import at.bitfire.vcard4android.ContactsStorageException import at.bitfire.vcard4android.GroupMethod import com.etesync.journalmanager.Crypto import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.utils.Base64 import java.net.URI import java.net.URISyntaxException import java.util.logging.Level class AccountSettings @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Throws(InvalidAccountException::class) constructor(internal val context: Context, internal val account: Account) { internal val accountManager: AccountManager // authentication settings var uri: URI? get() { val uri = accountManager.getUserData(account, KEY_URI) if (uri == null) { return null } try { return URI(uri) } catch (e: URISyntaxException) { return null } } set(uri) = accountManager.setUserData(account, KEY_URI, uri.toString()) var authToken: String get() = accountManager.getUserData(account, KEY_TOKEN) set(token) = accountManager.setUserData(account, KEY_TOKEN, token) var keyPair: Crypto.AsymmetricKeyPair? get() { if (accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY) != null) { val pubkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY), Base64.NO_WRAP) val privkey = Base64.decode(accountManager.getUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY), Base64.NO_WRAP) return Crypto.AsymmetricKeyPair(privkey, pubkey) } return null } set(keyPair) { accountManager.setUserData(account, KEY_ASYMMETRIC_PUBLIC_KEY, Base64.encodeToString(keyPair?.publicKey, Base64.NO_WRAP)) accountManager.setUserData(account, KEY_ASYMMETRIC_PRIVATE_KEY, Base64.encodeToString(keyPair?.privateKey, Base64.NO_WRAP)) } val syncWifiOnly: Boolean get() = accountManager.getUserData(account, KEY_WIFI_ONLY) != null var syncWifiOnlySSID: String? get() = accountManager.getUserData(account, KEY_WIFI_ONLY_SSID) set(ssid) = accountManager.setUserData(account, KEY_WIFI_ONLY_SSID, ssid) var etebaseSession: String? get() = accountManager.getUserData(account, KEY_ETEBASE_SESSION) set(value) = accountManager.setUserData(account, KEY_ETEBASE_SESSION, value) val isLegacy: Boolean get() = authToken != null // CalDAV settings var manageCalendarColors: Boolean get() = accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null set(manage) = accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0") // CardDAV settings var groupMethod: GroupMethod get() { val name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD) return if (name != null) GroupMethod.valueOf(name) else GroupMethod.GROUP_VCARDS } set(method) { val name = if (method == GroupMethod.GROUP_VCARDS) null else method.name accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name) } init { accountManager = AccountManager.get(context) synchronized(AccountSettings::class.java) { val versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION) ?: throw InvalidAccountException(account) var version = 0 try { version = Integer.parseInt(versionStr) } catch (ignored: NumberFormatException) { } Logger.log.fine("Account " + account.name + " has version " + version + ", current version: " + CURRENT_VERSION) if (version < CURRENT_VERSION) update(version) } } fun username(): String { return accountManager.getUserData(account, KEY_USERNAME) } fun username(userName: String) { accountManager.setUserData(account, KEY_USERNAME, userName) } fun password(): String { return accountManager.getPassword(account) } fun password(password: String) { accountManager.setPassword(account, password) } // sync. settings fun getSyncInterval(authority: String): Long? { if (ContentResolver.getIsSyncable(account, authority) <= 0) return null if (ContentResolver.getSyncAutomatically(account, authority)) { val syncs = ContentResolver.getPeriodicSyncs(account, authority) return if (syncs.isEmpty()) SYNC_INTERVAL_MANUALLY else syncs[0].period } else return SYNC_INTERVAL_MANUALLY } fun setSyncInterval(authority: String, seconds: Long) { if (seconds == SYNC_INTERVAL_MANUALLY) { ContentResolver.setSyncAutomatically(account, authority, false) } else { ContentResolver.setSyncAutomatically(account, authority, true) ContentResolver.addPeriodicSync(account, authority, Bundle(), seconds) } } fun setSyncWiFiOnly(wiFiOnly: Boolean) { accountManager.setUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null) } // update from previous account settings private fun update(fromVersion: Int) { val toVersion = CURRENT_VERSION Logger.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion) try { updateInner(fromVersion) accountManager.setUserData(account, KEY_SETTINGS_VERSION, toVersion.toString()) } catch (e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't update account settings", e) } } @Throws(ContactsStorageException::class) private fun updateInner(fromVersion: Int) { if (fromVersion < 2) { } } class AppUpdatedReceiver : BroadcastReceiver() { @SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission") override fun onReceive(context: Context, intent: Intent) { Logger.log.info("EteSync was updated, checking for AccountSettings version") // peek into AccountSettings to initiate a possible migration val accountManager = AccountManager.get(context) for (account in accountManager.getAccountsByType(App.accountType)) try { Logger.log.info("Checking account " + account.name) AccountSettings(context, account) } catch (e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Couldn't check for updated account settings", e) } } } class AccountMigrationException(msg: String) : Exception(msg) companion object { private val CURRENT_VERSION = 2 private val KEY_SETTINGS_VERSION = "version" private val KEY_URI = "uri" private val KEY_USERNAME = "user_name" private val KEY_TOKEN = "auth_token" private val KEY_ASYMMETRIC_PRIVATE_KEY = "asymmetric_private_key" private val KEY_ASYMMETRIC_PUBLIC_KEY = "asymmetric_public_key" private val KEY_WIFI_ONLY = "wifi_only" private val KEY_ETEBASE_SESSION = "etebase_session" // sync on WiFi only (default: false) private val KEY_WIFI_ONLY_SSID = "wifi_only_ssid" // restrict sync to specific WiFi SSID /** * Time range limitation to the past [in days] * value = null default value (DEFAULT_TIME_RANGE_PAST_DAYS) * < 0 (-1) no limit * >= 0 entries more than n days in the past won't be synchronized */ private val KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days" private val DEFAULT_TIME_RANGE_PAST_DAYS = 90 /* Whether DAVdroid sets the local calendar color to the value from service DB at every sync value = null (not existing) true (default) "0" false */ private val KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors" /** * Contact group method: * value = null (not existing) groups as separate VCards (default) * "CATEGORIES" groups are per-contact CATEGORIES */ private val KEY_CONTACT_GROUP_METHOD = "contact_group_method" val SYNC_INTERVAL_MANUALLY: Long = -1 // XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work. fun setUserData(accountManager: AccountManager, account: Account, uri: URI?, userName: String) { accountManager.setUserData(account, KEY_SETTINGS_VERSION, CURRENT_VERSION.toString()) accountManager.setUserData(account, KEY_USERNAME, userName) accountManager.setUserData(account, KEY_URI, uri?.toString()) } } }