1
0
mirror of https://github.com/etesync/android synced 2024-11-22 07:58:09 +00:00

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
This commit is contained in:
Tom Hacohen 2019-04-16 16:39:34 +01:00
parent f629d23c38
commit 3b0bfbb054
6 changed files with 108 additions and 195 deletions

View File

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

View File

@ -35,7 +35,6 @@ class AccountUpdateService : Service() {
val action = intent.action val action = intent.action
when (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"
}
} }

View File

@ -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)
}
}
}

View File

@ -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 * All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0 * are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
@ -8,65 +8,110 @@
package com.etesync.syncadapter.syncadapter package com.etesync.syncadapter.syncadapter
import android.accounts.* import android.accounts.*
import android.annotation.SuppressLint
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.DatabaseUtils
import android.os.Bundle 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 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() { override fun onCreate() {
accountManager = AccountManager.get(this)
accountManager.addOnAccountsUpdatedListener(this, null, true)
accountAuthenticator = AccountAuthenticator(this) accountAuthenticator = AccountAuthenticator(this)
} }
override fun onBind(intent: Intent): IBinder? { override fun onDestroy() {
return if (intent.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT) accountAuthenticator!!.iBinder else null super.onDestroy()
accountManager.removeOnAccountsUpdatedListener(this)
}
override fun onBind(intent: Intent?) =
accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT }
override fun onAccountsUpdated(accounts: Array<out Account>?) {
cleanupAccounts(this)
} }
private class AccountAuthenticator(internal val context: Context) : AbstractAccountAuthenticator(context) { 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, LoginActivity::class.java) val intent = Intent(context, LoginActivity::class.java)
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
val bundle = Bundle() val bundle = Bundle(1)
bundle.putParcelable(AccountManager.KEY_INTENT, intent) bundle.putParcelable(AccountManager.KEY_INTENT, intent)
return bundle return bundle
} }
@Throws(NetworkErrorException::class) override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = null
override fun confirmCredentials(response: AccountAuthenticatorResponse, account: Account, options: Bundle): Bundle? { override fun getAuthTokenLabel(p0: String?) = null
return 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 editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? { override fun hasFeatures(p0: AccountAuthenticatorResponse?, p1: Account?, p2: Array<out String>?) = null
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
}
} }
} }

View File

@ -5,75 +5,49 @@
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html * http://www.gnu.org/licenses/gpl.html
*/ */
package com.etesync.syncadapter.syncadapter 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.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.IBinder
import com.etesync.syncadapter.ui.AccountsActivity 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() { override fun onCreate() {
accountAuthenticator = NullAuthenticatorService.AccountAuthenticator(this) accountAuthenticator = AccountAuthenticator(this)
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent?) =
return if (intent.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT) accountAuthenticator!!.iBinder else null accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT }
}
private class AccountAuthenticator(internal val context: Context) : AbstractAccountAuthenticator(context) { private class AccountAuthenticator(
val context: Context
): AbstractAccountAuthenticator(context) {
override fun editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? { override fun addAccount(response: AccountAuthenticatorResponse?, accountType: String?, authTokenType: String?, requiredFeatures: Array<String>?, options: Bundle?): Bundle {
return null
}
@Throws(NetworkErrorException::class)
override fun addAccount(response: AccountAuthenticatorResponse, accountType: String, authTokenType: String, requiredFeatures: Array<String>, options: Bundle): Bundle {
val intent = Intent(context, AccountsActivity::class.java) val intent = Intent(context, AccountsActivity::class.java)
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
val bundle = Bundle() val bundle = Bundle(1)
bundle.putParcelable(AccountManager.KEY_INTENT, intent) bundle.putParcelable(AccountManager.KEY_INTENT, intent)
return bundle return bundle
} }
@Throws(NetworkErrorException::class) override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = null
override fun confirmCredentials(response: AccountAuthenticatorResponse, account: Account, options: Bundle): Bundle? { override fun getAuthTokenLabel(p0: String?) = null
return 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 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
}
@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
}
} }
} }

View File

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