Skip to content

Commit

Permalink
At/some concurrency playarounds (#64)
Browse files Browse the repository at this point in the history
* supporting futures and callbacks
  • Loading branch information
andretietz authored Apr 13, 2018
1 parent fe9eb9e commit 7e5cc4e
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.widget.Toast
import com.andretietz.retroauth.AndroidOwnerManager
import com.andretietz.retroauth.AndroidToken
import com.andretietz.retroauth.AndroidTokenStorage
import com.andretietz.retroauth.Callback
import com.andretietz.retroauth.RetroauthAndroidBuilder
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
Expand Down Expand Up @@ -70,19 +71,27 @@ class MainActivity : AppCompatActivity() {

buttonInvalidateToken.setOnClickListener {
ownerManager.getActiveOwner(provider.ownerType)?.let { account ->
val token = tokenStorage.getToken(account, provider.tokenType)
tokenStorage.storeToken(
account,
provider.tokenType,
AndroidToken("some-invalid-token", token.data)
)
tokenStorage.getToken(account, provider.tokenType, object : Callback<AndroidToken> {
override fun onResult(result: AndroidToken?) {
tokenStorage.storeToken(
account,
provider.tokenType,
AndroidToken("some-invalid-token", result?.data))
}

})
}
}

buttonLogout.setOnClickListener {
ownerManager.getActiveOwner(provider.ownerType)?.let { account ->
val token = tokenStorage.getToken(account, provider.tokenType)
tokenStorage.removeToken(account, provider.tokenType, token)
tokenStorage.getToken(account, provider.tokenType, object : Callback<AndroidToken> {
override fun onResult(result: AndroidToken?) {
result?.let {
tokenStorage.removeToken(account, provider.tokenType, it)
}
}
})
}
/** remove all cookies to avoid an automatic relogin */
val cookieManager = CookieManager.getInstance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ class ProviderFacebook(application: Application)
// https://developers.facebook.com/docs/facebook-login/access-tokens/refreshing
// `At any point, you can generate a new long-lived token by sending the
// person back to the login flow used by your web app.`
return tokenStorage.getToken(owner, tokenType)
return tokenStorage.getToken(owner, tokenType).get()
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ okhttpVersion=3.10.0
retrofitVersion=2.3.0

GROUP=com.andretietz.retroauth
VERSION_NAME=3.0.0-beta2-SNAPSHOT
VERSION_NAME=3.0.0-beta3-SNAPSHOT
POM_DESCRIPTION=A library build on top of retrofit, for simple handling of authenticated requests.
POM_URL=https://github.com/andretietz/retroauth
POM_SCM_URL=https://github.com/andretietz/retroauth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import android.os.Bundle
import android.os.Looper
import android.view.WindowManager
import java.util.concurrent.Callable
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.Future
import java.util.concurrent.FutureTask
import java.util.concurrent.TimeUnit


/**
Expand All @@ -52,7 +53,9 @@ class AndroidOwnerManager constructor(
private val accountManager by lazy { AccountManager.get(application) }

@Throws(AuthenticationCanceledException::class)
override fun createOwner(ownerType: String, tokenType: AndroidTokenType, callback: OwnerManager.Callback?): Account {
override fun createOwner(ownerType: String,
tokenType: AndroidTokenType,
callback: Callback<Account>?): Future<Account> {
val future = accountManager.addAccount(
ownerType,
tokenType.tokenType,
Expand All @@ -61,13 +64,7 @@ class AndroidOwnerManager constructor(
activityManager.activity,
if (callback != null) CreateAccountCallback(callback) else null,
null)
val result = future.result
val accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME)
if (accountName != null) {
return Account(result.getString(AccountManager.KEY_ACCOUNT_NAME),
result.getString(AccountManager.KEY_ACCOUNT_TYPE))
}
throw AuthenticationCanceledException()
return AccountFuture(future)
}

override fun getOwner(ownerType: String, ownerName: String): Account? {
Expand All @@ -86,14 +83,14 @@ class AndroidOwnerManager constructor(
return null
}

override fun openOwnerPicker(ownerType: String): Account? {
showAccountPickerDialog(ownerType)?.let {
getOwner(ownerType, it)?.let {
switchActiveOwner(it.type, it)
return it
}
override fun openOwnerPicker(ownerType: String, callback: Callback<Account?>?): Future<Account?> {
val task = ShowDialogPickerTask(application, accountManager, ownerType, callback)
if (Looper.myLooper() == Looper.getMainLooper()) {
return executor.submit(task)
}
return null
val future = FutureTask(task)
future.run()
return future
}

override fun switchActiveOwner(ownerType: String, owner: Account?) {
Expand All @@ -105,69 +102,60 @@ class AndroidOwnerManager constructor(
}
}

override fun removeOwner(owner: Account, callback: OwnerManager.Callback?) {
override fun removeOwner(owner: Account, callback: Callback<Boolean>?): Future<Boolean> {
val accountFuture: Future<Boolean>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val rac = if (callback != null) RemoveLollipopAccountCallback(callback) else null
accountManager.removeAccount(owner, null, rac, null)
accountFuture = RemoveAccountFuture(accountManager.removeAccount(owner, null, rac, null))
} else {
val rac = if (callback != null) RemoveAccountCallback(callback) else null
@Suppress("DEPRECATION")
accountManager.removeAccount(owner, rac, null)
accountFuture = PreLollipopRemoveAccountFuture(accountManager.removeAccount(owner, rac, null))
}
switchActiveOwner(owner.type)
return accountFuture
}

/**
* Shows an account picker for the user to choose an account. Make sure you're calling this from a non-ui thread
*
* @param accountType Account type of the accounts the user can choose
* @param canAddAccount if `true` the user has the option to add an account
* @return the accounts the user chooses from
*/
@Throws(AuthenticationCanceledException::class)
private fun showAccountPickerDialog(accountType: String): String? {
val task = ShowDialogPickerTask(application, accountManager, accountType)
if (Looper.myLooper() == Looper.getMainLooper()) {
return executor.submit(task).get()
}
return task.call()
}


private class ShowDialogPickerTask(
private val application: Application,
private val accountManager: AccountManager,
private val accountType: String

) : Callable<String?> {
private val accountType: String,
private val callback: Callback<Account?>?
) : Callable<Account?> {

private val activityManager = ActivityManager[application]

override fun call(): String? {
override fun call(): Account? {
val accounts = accountManager.getAccountsByType(accountType)
if (accounts.isEmpty()) return null
val accountList = accounts.map { it.name }.toMutableSet()
accountList.add(application.getString(R.string.add_account_button_label))
val lock = ReentrantLock()
val condition = lock.newCondition()
val countDownLatch = CountDownLatch(1)
val activity = activityManager.activity
// show the account chooser
val showDialog = ShowAccountChooser(application, activityManager, accountList.toTypedArray(), lock, condition)
val showDialog = ShowAccountChooser(
application,
activityManager,
accountList.toTypedArray(),
countDownLatch)
activity?.let {
activity.runOnUiThread(showDialog)
lock.lock()
try {
condition.await()
} finally {
lock.unlock()
}
countDownLatch.await()
}
if (showDialog.canceled) {
throw AuthenticationCanceledException("User canceled authentication!")
}
return showDialog.selectedOption
val accountName = showDialog.selectedOption
for (account in accounts) {
if (accountName == account.name) {
val preferences = application.getSharedPreferences(accountType, Context.MODE_PRIVATE)
preferences.edit().putString(RETROAUTH_ACCOUNT_NAME_KEY, account.name).apply()
callback?.onResult(account)
return account
}
}
return null
}

}

/**
Expand All @@ -177,24 +165,14 @@ class AndroidOwnerManager constructor(
private val context: Context,
private val activityManager: ActivityManager,
private val options: Array<String>,
private val lock: Lock,
private val condition: Condition) : Runnable {
private val countDownLatch: CountDownLatch) : Runnable {
internal var canceled = false
var selectedOption: String? = null

init {
this.selectedOption = options[0]
}

private fun unlock() {
lock.lock()
try {
condition.signal()
} finally {
lock.unlock()
}
}

override fun run() {
val builder = AlertDialog.Builder(activityManager.activity)
.setTitle(context.getString(R.string.choose_account_label))
Expand All @@ -207,7 +185,7 @@ class AndroidOwnerManager constructor(
.setNegativeButton(android.R.string.cancel) { _, _ -> canceled = true }
.setPositiveButton(android.R.string.ok) { _, _ -> canceled = false }
val dialog = builder.create()
dialog.setOnDismissListener { unlock() }
dialog.setOnDismissListener { countDownLatch.countDown() }
dialog.show()
keep(dialog)
}
Expand All @@ -225,45 +203,83 @@ class AndroidOwnerManager constructor(
/**
* Callback wrapper for adding an account
*/
private class CreateAccountCallback(private val callback: OwnerManager.Callback) : AccountManagerCallback<Bundle> {
private class CreateAccountCallback(private val callback: Callback<Account>) : AccountManagerCallback<Bundle> {

override fun run(accountManagerFuture: AccountManagerFuture<Bundle>) {
try {
val accountName = accountManagerFuture.result.getString(AccountManager.KEY_ACCOUNT_NAME)
callback.done(accountName != null)
} catch (e: Exception) {
callback.done(false)
val accountName = accountManagerFuture.result.getString(AccountManager.KEY_ACCOUNT_NAME)
if (accountName != null) {
callback.onResult(Account(accountName,
accountManagerFuture.result.getString(AccountManager.KEY_ACCOUNT_TYPE)))
return
}
}
}

/**
* Callback wrapper for account removing on >= lollipop (22) devices
*/
private class RemoveLollipopAccountCallback(private val callback: OwnerManager.Callback)
private class RemoveLollipopAccountCallback(private val callback: Callback<Boolean>)
: AccountManagerCallback<Bundle> {

override fun run(accountManagerFuture: AccountManagerFuture<Bundle>) {
try {
callback.done(accountManagerFuture.result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
callback.onResult(accountManagerFuture.result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
} catch (e: Exception) {
callback.done(false)
callback.onResult(false)
}
}
}

/**
* Callback wrapper for account removing on prelollipop (22 -> MR1) devices
*/
private class RemoveAccountCallback(private val callback: OwnerManager.Callback) : AccountManagerCallback<Boolean> {
private class RemoveAccountCallback(private val callback: Callback<Boolean>) : AccountManagerCallback<Boolean> {

override fun run(accountManagerFuture: AccountManagerFuture<Boolean>) {
try {
callback.done(accountManagerFuture.result)
callback.onResult(accountManagerFuture.result)
} catch (e: Exception) {
callback.done(false)
callback.onResult(false)
}

}
}

private class AccountFuture(
private val accountFuture: AccountManagerFuture<Bundle>
) : Future<Account> {
override fun isDone(): Boolean = accountFuture.isDone
override fun get(): Account = createAccount(accountFuture.result)
override fun get(p0: Long, p1: TimeUnit?): Account = createAccount(accountFuture.getResult(p0, p1))
private fun createAccount(bundle: Bundle): Account {
val accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME)
if (accountName != null) {
return Account(bundle.getString(AccountManager.KEY_ACCOUNT_NAME),
bundle.getString(AccountManager.KEY_ACCOUNT_TYPE))
}
throw AuthenticationCanceledException()
}

override fun cancel(p0: Boolean): Boolean = accountFuture.cancel(p0)
override fun isCancelled(): Boolean = accountFuture.isCancelled
}

private class RemoveAccountFuture(private val accountFuture: AccountManagerFuture<Bundle>) : Future<Boolean> {
override fun isDone(): Boolean = accountFuture.isDone
override fun get(): Boolean = accountFuture.result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)
override fun get(p0: Long, p1: TimeUnit?): Boolean = accountFuture.getResult(p0, p1)
.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)

override fun cancel(p0: Boolean): Boolean = accountFuture.cancel(p0)
override fun isCancelled(): Boolean = accountFuture.isCancelled
}

private class PreLollipopRemoveAccountFuture(private val accountFuture: AccountManagerFuture<Boolean>)
: Future<Boolean> {
override fun isDone(): Boolean = accountFuture.isDone
override fun get(): Boolean = accountFuture.result
override fun get(p0: Long, p1: TimeUnit?): Boolean = accountFuture.getResult(p0, p1)
override fun cancel(p0: Boolean): Boolean = accountFuture.cancel(p0)
override fun isCancelled(): Boolean = accountFuture.isCancelled
}
}
Loading

0 comments on commit 7e5cc4e

Please sign in to comment.