Don't rely on the ACCOUNTS_CHANGED broadcast receiver

I've seen some crashes there. This change brings it inline with
DAVDroid, and looks cleaner regardless.

Based on 1f7298f947a4878e86fcba0c6722e34b03cb63c6 from DAVDroid
pull/96/head
Tom Hacohen 5 years ago
parent f629d23c38
commit 3b0bfbb054

@ -164,12 +164,6 @@
android:enabled="true">
</service>
<receiver android:name=".AccountsChangedReceiver">
<intent-filter>
<action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name=".PackageChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>

@ -35,7 +35,6 @@ class AccountUpdateService : Service() {
val action = intent.action
when (action) {
ACTION_ACCOUNTS_UPDATED -> cleanupAccounts()
}
}
@ -77,45 +76,4 @@ class AccountUpdateService : Service() {
}
}
/* ACTION RUNNABLES
which actually do the work
*/
@SuppressLint("MissingPermission")
internal fun cleanupAccounts() {
Logger.log.info("Cleaning up orphaned accounts")
val accountNames = LinkedList<String>()
val am = AccountManager.get(this)
for (account in am.getAccountsByType(getString(R.string.account_type))) {
accountNames.add(account.name)
}
val data = (application as App).data
// delete orphaned address book accounts
for (addrBookAccount in am.getAccountsByType(getString(R.string.account_type_address_book))) {
val addressBook = LocalAddressBook(this, addrBookAccount, null)
try {
if (!accountNames.contains(addressBook.mainAccount.name))
addressBook.delete()
} catch (e: ContactsStorageException) {
Logger.log.log(Level.SEVERE, "Couldn't get address book main account", e)
}
}
if (accountNames.isEmpty()) {
data.delete(ServiceEntity::class.java).get().value()
} else {
data.delete(ServiceEntity::class.java).where(ServiceEntity.ACCOUNT.notIn(accountNames)).get().value()
}
}
companion object {
val ACTION_ACCOUNTS_UPDATED = "accountsUpdated"
}
}

@ -1,51 +0,0 @@
/*
* Copyright © 2013 2016 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.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.etesync.syncadapter.log.Logger
import java.util.*
class AccountsChangedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION == intent.action) {
val serviceIntent = Intent(context, AccountUpdateService::class.java)
serviceIntent.action = AccountUpdateService.ACTION_ACCOUNTS_UPDATED
try {
context.startService(serviceIntent)
} catch (e: IllegalStateException) {
Logger.log.warning("Got an illegal state exception! Ignoring...")
}
for (listener in listeners)
listener.onAccountsUpdated(null)
}
}
companion object {
protected val listeners: MutableList<OnAccountsUpdateListener> = LinkedList()
fun registerListener(listener: OnAccountsUpdateListener, callImmediately: Boolean) {
listeners.add(listener)
if (callImmediately)
listener.onAccountsUpdated(null)
}
fun unregisterListener(listener: OnAccountsUpdateListener) {
listeners.remove(listener)
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright © 2013 2015 Ricki Hirner (bitfire web engineering).
* 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
@ -8,65 +8,110 @@
package com.etesync.syncadapter.syncadapter
import android.accounts.*
import android.annotation.SuppressLint
import android.app.Service
import android.content.Context
import android.content.Intent
import android.database.DatabaseUtils
import android.os.Bundle
import android.os.IBinder
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.App
import com.etesync.syncadapter.R
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.ServiceDB
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.resource.LocalAddressBook
import com.etesync.syncadapter.ui.setup.LoginActivity
import java.util.*
import java.util.logging.Level
/**
* Account authenticator for the main DAVx5 account type.
*
* Gets started when a DAVx5 account is removed, too, so it also watches for account removals
* and contains the corresponding cleanup code.
*/
class AccountAuthenticatorService: Service(), OnAccountsUpdateListener {
companion object {
@SuppressLint("MissingPermission")
internal fun cleanupAccounts(context: Context) {
Logger.log.info("Cleaning up orphaned accounts")
val accountNames = LinkedList<String>()
val am = AccountManager.get(context)
for (account in am.getAccountsByType(context.getString(R.string.account_type))) {
accountNames.add(account.name)
}
val data = (context.applicationContext as App).data
// delete orphaned address book accounts
for (addrBookAccount in am.getAccountsByType(context.getString(R.string.account_type_address_book))) {
val addressBook = LocalAddressBook(context, addrBookAccount, null)
try {
if (!accountNames.contains(addressBook.mainAccount.name))
addressBook.delete()
} catch (e: ContactsStorageException) {
Logger.log.log(Level.SEVERE, "Couldn't get address book main account", e)
}
}
if (accountNames.isEmpty()) {
data.delete(ServiceEntity::class.java).get().value()
} else {
data.delete(ServiceEntity::class.java).where(ServiceEntity.ACCOUNT.notIn(accountNames)).get().value()
}
}
}
private lateinit var accountManager: AccountManager
private lateinit var accountAuthenticator: AccountAuthenticator
class AccountAuthenticatorService : Service() {
private var accountAuthenticator: AccountAuthenticator? = null
override fun onCreate() {
accountManager = AccountManager.get(this)
accountManager.addOnAccountsUpdatedListener(this, null, true)
accountAuthenticator = AccountAuthenticator(this)
}
override fun onBind(intent: Intent): IBinder? {
return if (intent.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT) accountAuthenticator!!.iBinder else null
override fun onDestroy() {
super.onDestroy()
accountManager.removeOnAccountsUpdatedListener(this)
}
override fun onBind(intent: Intent?) =
accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT }
private class AccountAuthenticator(internal val context: Context) : AbstractAccountAuthenticator(context) {
@Throws(NetworkErrorException::class)
override fun addAccount(response: AccountAuthenticatorResponse, accountType: String, authTokenType: String,
requiredFeatures: Array<String>, options: Bundle): Bundle {
override fun onAccountsUpdated(accounts: Array<out Account>?) {
cleanupAccounts(this)
}
private class AccountAuthenticator(
val context: Context
): AbstractAccountAuthenticator(context) {
override fun addAccount(response: AccountAuthenticatorResponse?, accountType: String?, authTokenType: String?, requiredFeatures: Array<String>?, options: Bundle?): Bundle {
val intent = Intent(context, LoginActivity::class.java)
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
val bundle = Bundle()
val bundle = Bundle(1)
bundle.putParcelable(AccountManager.KEY_INTENT, intent)
return bundle
}
@Throws(NetworkErrorException::class)
override fun confirmCredentials(response: AccountAuthenticatorResponse, account: Account, options: Bundle): Bundle? {
return null
}
override fun editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? {
return null
}
@Throws(NetworkErrorException::class)
override fun getAuthToken(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
return null
}
override fun getAuthTokenLabel(authTokenType: String): String? {
return null
}
@Throws(NetworkErrorException::class)
override fun hasFeatures(response: AccountAuthenticatorResponse, account: Account, features: Array<String>): Bundle? {
return null
}
@Throws(NetworkErrorException::class)
override fun updateCredentials(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
return null
}
override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = null
override fun getAuthTokenLabel(p0: String?) = null
override fun confirmCredentials(p0: AccountAuthenticatorResponse?, p1: Account?, p2: Bundle?) = null
override fun updateCredentials(p0: AccountAuthenticatorResponse?, p1: Account?, p2: String?, p3: Bundle?) = null
override fun getAuthToken(p0: AccountAuthenticatorResponse?, p1: Account?, p2: String?, p3: Bundle?) = null
override fun hasFeatures(p0: AccountAuthenticatorResponse?, p1: Account?, p2: Array<out String>?) = null
}
}

@ -5,75 +5,49 @@
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter.syncadapter
import android.accounts.*
import android.accounts.AbstractAccountAuthenticator
import android.accounts.Account
import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.IBinder
import com.etesync.syncadapter.ui.AccountsActivity
class NullAuthenticatorService : Service() {
class NullAuthenticatorService: Service() {
private var accountAuthenticator: AccountAuthenticator? = null
private lateinit var accountAuthenticator: AccountAuthenticator
override fun onCreate() {
accountAuthenticator = NullAuthenticatorService.AccountAuthenticator(this)
}
override fun onBind(intent: Intent): IBinder? {
return if (intent.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT) accountAuthenticator!!.iBinder else null
accountAuthenticator = AccountAuthenticator(this)
}
override fun onBind(intent: Intent?) =
accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT }
private class AccountAuthenticator(internal val context: Context) : AbstractAccountAuthenticator(context) {
override fun editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? {
return null
}
private class AccountAuthenticator(
val context: Context
): AbstractAccountAuthenticator(context) {
@Throws(NetworkErrorException::class)
override fun addAccount(response: AccountAuthenticatorResponse, accountType: String, authTokenType: String, requiredFeatures: Array<String>, options: Bundle): Bundle {
override fun addAccount(response: AccountAuthenticatorResponse?, accountType: String?, authTokenType: String?, requiredFeatures: Array<String>?, options: Bundle?): Bundle {
val intent = Intent(context, AccountsActivity::class.java)
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
val bundle = Bundle()
val bundle = Bundle(1)
bundle.putParcelable(AccountManager.KEY_INTENT, intent)
return bundle
}
@Throws(NetworkErrorException::class)
override fun confirmCredentials(response: AccountAuthenticatorResponse, account: Account, options: Bundle): Bundle? {
return null
}
@Throws(NetworkErrorException::class)
override fun getAuthToken(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
return null
}
override fun getAuthTokenLabel(authTokenType: String): String? {
return null
}
@Throws(NetworkErrorException::class)
override fun updateCredentials(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
return null
}
override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = null
override fun getAuthTokenLabel(p0: String?) = null
override fun confirmCredentials(p0: AccountAuthenticatorResponse?, p1: Account?, p2: Bundle?) = null
override fun updateCredentials(p0: AccountAuthenticatorResponse?, p1: Account?, p2: String?, p3: Bundle?) = null
override fun getAuthToken(p0: AccountAuthenticatorResponse?, p1: Account?, p2: String?, p3: Bundle?) = null
override fun hasFeatures(p0: AccountAuthenticatorResponse?, p1: Account?, p2: Array<out String>?) = null
@Throws(NetworkErrorException::class)
override fun hasFeatures(response: AccountAuthenticatorResponse, account: Account, features: Array<String>): Bundle? {
return null
}
override fun getAccountRemovalAllowed(response: AccountAuthenticatorResponse, account: Account): Bundle {
val result = Bundle()
val allowed = false // we don't want users to explicitly delete inner accounts
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, allowed)
return result
}
}
}

@ -26,7 +26,6 @@ import android.widget.AbsListView
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import com.etesync.syncadapter.AccountsChangedReceiver
import com.etesync.syncadapter.App
import com.etesync.syncadapter.R
@ -73,19 +72,13 @@ class AccountListFragment : ListFragment(), LoaderManager.LoaderCallbacks<Array<
}
private class AccountLoader(context: Context) : AsyncTaskLoader<Array<Account>>(context), OnAccountsUpdateListener {
private val accountManager: AccountManager
private val accountManager = AccountManager.get(context)
init {
accountManager = AccountManager.get(context)
}
override fun onStartLoading() {
AccountsChangedReceiver.registerListener(this, true)
}
override fun onStartLoading() =
accountManager.addOnAccountsUpdatedListener(this, null, true)
override fun onStopLoading() {
AccountsChangedReceiver.unregisterListener(this)
}
override fun onStopLoading() =
accountManager.removeOnAccountsUpdatedListener(this)
override fun onAccountsUpdated(accounts: Array<Account>) {
forceLoad()

Loading…
Cancel
Save