Implement account settings and password change.

pull/131/head
Tom Hacohen 4 years ago
parent a2a9a3e08c
commit 1bdd4d78f4

@ -153,6 +153,7 @@ class EtebaseLocalCache private constructor(context: Context, username: String)
} }
} }
// FIXME: If we ever cache this we need to cache bust on changePassword
fun getEtebase(context: Context, httpClient: OkHttpClient, settings: AccountSettings): Account { fun getEtebase(context: Context, httpClient: OkHttpClient, settings: AccountSettings): Account {
val client = Client.create(httpClient, settings.uri?.toString()) val client = Client.create(httpClient, settings.uri?.toString())
return Account.restore(client, settings.etebaseSession!!, null) return Account.restore(client, settings.etebaseSession!!, null)

@ -18,11 +18,11 @@ import android.provider.CalendarContract
import android.text.TextUtils import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
import androidx.core.app.NavUtils import androidx.core.app.NavUtils
import androidx.fragment.app.Fragment
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.preference.* import androidx.preference.*
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import com.etesync.syncadapter.* import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT import com.etesync.syncadapter.Constants.KEY_ACCOUNT
@ -43,7 +43,8 @@ class AccountSettingsActivity : BaseActivity() {
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null) { if (savedInstanceState == null) {
val frag = AccountSettingsFragment() val settings = AccountSettings(this, account)
val frag: Fragment = if (settings.isLegacy) LegacyAccountSettingsFragment() else AccountSettingsFragment()
frag.arguments = intent.extras frag.arguments = intent.extras
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(android.R.id.content, frag) .replace(android.R.id.content, frag)
@ -60,139 +61,226 @@ class AccountSettingsActivity : BaseActivity() {
} else } else
return false return false
} }
}
class AccountSettingsFragment : PreferenceFragmentCompat(), LoaderManager.LoaderCallbacks<AccountSettings> { class AccountSettingsFragment() : PreferenceFragmentCompat(), LoaderManager.LoaderCallbacks<AccountSettings> {
internal lateinit var account: Account internal lateinit var account: Account
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
account = arguments?.getParcelable(KEY_ACCOUNT)!! account = arguments?.getParcelable(KEY_ACCOUNT)!!
loaderManager.initLoader(0, arguments, this) loaderManager.initLoader(0, arguments, this)
} }
override fun onCreatePreferences(bundle: Bundle, s: String) { override fun onCreatePreferences(bundle: Bundle, s: String) {
addPreferencesFromResource(R.xml.settings_account) addPreferencesFromResource(R.xml.settings_account)
} }
override fun onCreateLoader(id: Int, args: Bundle?): Loader<AccountSettings> { override fun onCreateLoader(id: Int, args: Bundle?): Loader<AccountSettings> {
return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account) return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
} }
override fun onLoadFinished(loader: Loader<AccountSettings>, settings: AccountSettings?) { override fun onLoadFinished(loader: Loader<AccountSettings>, settings: AccountSettings?) {
if (settings == null) { if (settings == null) {
activity!!.finish() activity!!.finish()
return return
} }
// Category: dashboard
val prefManageAccount = findPreference("manage_account")
prefManageAccount.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
WebViewActivity.openUrl(activity!!, Constants.dashboard.buildUpon().appendQueryParameter("email", account.name).build())
true
}
// Category: dashboard // Category: encryption
val prefManageAccount = findPreference("manage_account") val prefEncryptionPassword = findPreference("password")
prefManageAccount.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ -> prefEncryptionPassword.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
WebViewActivity.openUrl(activity!!, Constants.dashboard.buildUpon().appendQueryParameter("email", account.name).build()) startActivity(ChangeEncryptionPasswordActivity.newIntent(activity!!, account))
true true
} }
// category: authentication val prefSync = findPreference("sync_interval") as ListPreference
val prefPassword = findPreference("password") as EditTextPreference val syncInterval = settings.getSyncInterval(CalendarContract.AUTHORITY) // Calendar is the baseline interval
prefPassword.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> if (syncInterval != null) {
val credentials = if (newValue != null) LoginCredentials(settings.uri, account.name, newValue as String) else null prefSync.value = syncInterval.toString()
LoginCredentialsChangeFragment.newInstance(account, credentials!!).show(fragmentManager!!, null) if (syncInterval == AccountSettings.SYNC_INTERVAL_MANUALLY)
prefSync.setSummary(R.string.settings_sync_summary_manually)
else
prefSync.summary = getString(R.string.settings_sync_summary_periodically, prefSync.entry)
prefSync.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val newInterval = java.lang.Long.parseLong(newValue as String)
settings.setSyncInterval(App.addressBooksAuthority, newInterval)
settings.setSyncInterval(CalendarContract.AUTHORITY, newInterval)
OPENTASK_PROVIDERS.forEach {
settings.setSyncInterval(it.authority, newInterval)
}
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment) loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false false
} }
} else {
prefSync.isEnabled = false
prefSync.setSummary(R.string.settings_sync_summary_not_available)
}
// Category: encryption val prefWifiOnly = findPreference("sync_wifi_only") as SwitchPreferenceCompat
val prefEncryptionPassword = findPreference("encryption_password") prefWifiOnly.isChecked = settings.syncWifiOnly
prefEncryptionPassword.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ -> prefWifiOnly.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, wifiOnly ->
startActivity(ChangeEncryptionPasswordActivity.newIntent(activity!!, account)) settings.setSyncWiFiOnly(wifiOnly as Boolean)
true loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
} false
}
val prefSync = findPreference("sync_interval") as ListPreference val prefWifiOnlySSID = findPreference("sync_wifi_only_ssid") as EditTextPreference
val syncInterval = settings.getSyncInterval(CalendarContract.AUTHORITY) // Calendar is the baseline interval val onlySSID = settings.syncWifiOnlySSID
if (syncInterval != null) { prefWifiOnlySSID.text = onlySSID
prefSync.value = syncInterval.toString() if (onlySSID != null)
if (syncInterval == AccountSettings.SYNC_INTERVAL_MANUALLY) prefWifiOnlySSID.summary = getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID)
prefSync.setSummary(R.string.settings_sync_summary_manually) else
else prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off)
prefSync.summary = getString(R.string.settings_sync_summary_periodically, prefSync.entry) prefWifiOnlySSID.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
prefSync.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> val ssid = newValue as String
val newInterval = java.lang.Long.parseLong(newValue as String) settings.syncWifiOnlySSID = if (!TextUtils.isEmpty(ssid)) ssid else null
settings.setSyncInterval(App.addressBooksAuthority, newInterval) loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
settings.setSyncInterval(CalendarContract.AUTHORITY, newInterval) false
OPENTASK_PROVIDERS.forEach { }
settings.setSyncInterval(it.authority, newInterval) }
}
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false
}
} else {
prefSync.isEnabled = false
prefSync.setSummary(R.string.settings_sync_summary_not_available)
}
val prefWifiOnly = findPreference("sync_wifi_only") as SwitchPreferenceCompat override fun onLoaderReset(loader: Loader<AccountSettings>) {}
prefWifiOnly.isChecked = settings.syncWifiOnly
prefWifiOnly.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, wifiOnly ->
settings.setSyncWiFiOnly(wifiOnly as Boolean)
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false
}
val prefWifiOnlySSID = findPreference("sync_wifi_only_ssid") as EditTextPreference }
val onlySSID = settings.syncWifiOnlySSID
prefWifiOnlySSID.text = onlySSID
if (onlySSID != null)
prefWifiOnlySSID.summary = getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID) class LegacyAccountSettingsFragment : PreferenceFragmentCompat(), LoaderManager.LoaderCallbacks<AccountSettings> {
else internal lateinit var account: Account
prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off)
prefWifiOnlySSID.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val ssid = newValue as String
settings.syncWifiOnlySSID = if (!TextUtils.isEmpty(ssid)) ssid else null
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false
}
}
override fun onLoaderReset(loader: Loader<AccountSettings>) {} override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
account = arguments?.getParcelable(KEY_ACCOUNT)!!
loaderManager.initLoader(0, arguments, this)
} }
override fun onCreatePreferences(bundle: Bundle, s: String) {
addPreferencesFromResource(R.xml.settings_account_legacy)
}
private class AccountSettingsLoader(context: Context, internal val account: Account) : AsyncTaskLoader<AccountSettings>(context), SyncStatusObserver { override fun onCreateLoader(id: Int, args: Bundle?): Loader<AccountSettings> {
internal lateinit var listenerHandle: Any return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
}
override fun onStartLoading() { override fun onLoadFinished(loader: Loader<AccountSettings>, settings: AccountSettings?) {
forceLoad() if (settings == null) {
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this) activity!!.finish()
return
} }
override fun onStopLoading() { // Category: dashboard
ContentResolver.removeStatusChangeListener(listenerHandle) val prefManageAccount = findPreference("manage_account")
prefManageAccount.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
WebViewActivity.openUrl(activity!!, Constants.dashboard.buildUpon().appendQueryParameter("email", account.name).build())
true
} }
override fun abandon() { // category: authentication
onStopLoading() val prefPassword = findPreference("password") as EditTextPreference
prefPassword.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val credentials = if (newValue != null) LoginCredentials(settings.uri, account.name, newValue as String) else null
LoginCredentialsChangeFragment.newInstance(account, credentials!!).show(fragmentManager!!, null)
loaderManager.restartLoader(0, arguments, this@LegacyAccountSettingsFragment)
false
} }
override fun loadInBackground(): AccountSettings? { // Category: encryption
val settings: AccountSettings val prefEncryptionPassword = findPreference("encryption_password")
try { prefEncryptionPassword.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
settings = AccountSettings(context, account) startActivity(ChangeEncryptionPasswordActivity.newIntent(activity!!, account))
} catch (e: InvalidAccountException) { true
return null }
}
return settings val prefSync = findPreference("sync_interval") as ListPreference
val syncInterval = settings.getSyncInterval(CalendarContract.AUTHORITY) // Calendar is the baseline interval
if (syncInterval != null) {
prefSync.value = syncInterval.toString()
if (syncInterval == AccountSettings.SYNC_INTERVAL_MANUALLY)
prefSync.setSummary(R.string.settings_sync_summary_manually)
else
prefSync.summary = getString(R.string.settings_sync_summary_periodically, prefSync.entry)
prefSync.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val newInterval = java.lang.Long.parseLong(newValue as String)
settings.setSyncInterval(App.addressBooksAuthority, newInterval)
settings.setSyncInterval(CalendarContract.AUTHORITY, newInterval)
OPENTASK_PROVIDERS.forEach {
settings.setSyncInterval(it.authority, newInterval)
}
loaderManager.restartLoader(0, arguments, this@LegacyAccountSettingsFragment)
false
}
} else {
prefSync.isEnabled = false
prefSync.setSummary(R.string.settings_sync_summary_not_available)
} }
override fun onStatusChanged(which: Int) { val prefWifiOnly = findPreference("sync_wifi_only") as SwitchPreferenceCompat
Logger.log.fine("Reloading account settings") prefWifiOnly.isChecked = settings.syncWifiOnly
forceLoad() prefWifiOnly.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, wifiOnly ->
settings.setSyncWiFiOnly(wifiOnly as Boolean)
loaderManager.restartLoader(0, arguments, this@LegacyAccountSettingsFragment)
false
} }
val prefWifiOnlySSID = findPreference("sync_wifi_only_ssid") as EditTextPreference
val onlySSID = settings.syncWifiOnlySSID
prefWifiOnlySSID.text = onlySSID
if (onlySSID != null)
prefWifiOnlySSID.summary = getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID)
else
prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off)
prefWifiOnlySSID.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val ssid = newValue as String
settings.syncWifiOnlySSID = if (!TextUtils.isEmpty(ssid)) ssid else null
loaderManager.restartLoader(0, arguments, this@LegacyAccountSettingsFragment)
false
}
} }
override fun onLoaderReset(loader: Loader<AccountSettings>) {}
} }
private class AccountSettingsLoader(context: Context, internal val account: Account) : AsyncTaskLoader<AccountSettings>(context), SyncStatusObserver {
internal lateinit var listenerHandle: Any
override fun onStartLoading() {
forceLoad()
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this)
}
override fun onStopLoading() {
ContentResolver.removeStatusChangeListener(listenerHandle)
}
override fun abandon() {
onStopLoading()
}
override fun loadInBackground(): AccountSettings? {
val settings: AccountSettings
try {
settings = AccountSettings(context, account)
} catch (e: InvalidAccountException) {
return null
}
return settings
}
override fun onStatusChanged(which: Int) {
Logger.log.fine("Reloading account settings")
forceLoad()
}
}

@ -15,6 +15,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.etebase.client.Client
import com.etesync.syncadapter.AccountSettings import com.etesync.syncadapter.AccountSettings
import com.etesync.syncadapter.HttpClient import com.etesync.syncadapter.HttpClient
import com.etesync.syncadapter.R import com.etesync.syncadapter.R
@ -53,7 +54,7 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.wrong_encryption_password) .setTitle(R.string.wrong_encryption_password)
.setIcon(R.drawable.ic_error_dark) .setIcon(R.drawable.ic_error_dark)
.setMessage(getString(R.string.wrong_encryption_password_content, e.localizedMessage)) .setMessage(e.localizedMessage)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
// dismiss // dismiss
}.show() }.show()
@ -62,6 +63,45 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() {
fun changePasswordDo(old_password: String, new_password: String) { fun changePasswordDo(old_password: String, new_password: String) {
val settings = AccountSettings(this, account) val settings = AccountSettings(this, account)
if (settings.isLegacy) {
legacyChangePasswordDo(settings, old_password, new_password)
return
}
doAsync {
val httpClient = HttpClient.sharedClient
try {
Logger.log.info("Loging in with old password")
val client = Client.create(httpClient, settings.uri?.toString())
val etebase = com.etebase.client.Account.login(client, account.name, old_password)
Logger.log.info("Login successful")
etebase.changePassword(new_password)
settings.etebaseSession = etebase.save(null)
uiThread {
progress.dismiss()
AlertDialog.Builder(this@ChangeEncryptionPasswordActivity)
.setTitle(R.string.change_encryption_password_success_title)
.setMessage(R.string.change_encryption_password_success_body)
.setPositiveButton(android.R.string.ok) { _, _ ->
this@ChangeEncryptionPasswordActivity.finish()
}.show()
requestSync(applicationContext, account)
}
} catch (e: Exception) {
uiThread {
changePasswordError(e)
}
return@doAsync
}
}
}
fun legacyChangePasswordDo(settings: AccountSettings, old_password: String, new_password: String) {
doAsync { doAsync {
val httpClient = HttpClient.Builder(this@ChangeEncryptionPasswordActivity, settings).setForeground(false).build().okHttpClient val httpClient = HttpClient.Builder(this@ChangeEncryptionPasswordActivity, settings).setForeground(false).build().okHttpClient

@ -18,25 +18,12 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/settings_encryption"> <PreferenceCategory android:title="@string/settings_sync">
<Preference <Preference
android:key="password"
android:title="@string/settings_encryption_password" android:title="@string/settings_encryption_password"
android:key="encryption_password"
android:summary="@string/settings_encryption_password_summary" android:summary="@string/settings_encryption_password_summary"
android:persistent="false" />
/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_sync">
<EditTextPreference
android:key="password"
android:title="@string/settings_password"
android:persistent="false"
android:inputType="textPassword"
android:summary="@string/settings_password_summary"
android:dialogTitle="@string/settings_enter_password" />
<ListPreference <ListPreference
android:key="sync_interval" android:key="sync_interval"

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 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
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/settings_manage_account">
<Preference
android:key="manage_account"
android:title="@string/settings_account_dashboard"
android:persistent="false"
android:summary="@string/settings_manage_account_summary" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_encryption">
<Preference
android:title="@string/settings_encryption_password"
android:key="encryption_password"
android:summary="@string/settings_encryption_password_summary"
android:persistent="false"
/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_sync">
<EditTextPreference
android:key="password"
android:title="@string/settings_password"
android:persistent="false"
android:inputType="textPassword"
android:summary="@string/settings_password_summary"
android:dialogTitle="@string/settings_enter_password" />
<ListPreference
android:key="sync_interval"
android:persistent="false"
android:title="@string/settings_sync_interval"
android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" />
<SwitchPreferenceCompat
android:key="sync_wifi_only"
android:persistent="false"
android:title="@string/settings_sync_wifi_only"
android:summaryOn="@string/settings_sync_wifi_only_on"
android:summaryOff="@string/settings_sync_wifi_only_off"
/>
<EditTextPreference
android:key="sync_wifi_only_ssid"
android:dependency="sync_wifi_only"
android:persistent="false"
android:title="@string/settings_sync_wifi_only_ssid"
android:dialogMessage="@string/settings_sync_wifi_only_ssid_message"/>
</PreferenceCategory>
</PreferenceScreen>
Loading…
Cancel
Save