2016-07-07 09:39:32 +02:00
|
|
|
/*
|
|
|
|
* Twidere - Twitter client for Android
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package org.mariotaku.twidere.activity
|
|
|
|
|
2016-12-03 06:48:40 +01:00
|
|
|
import android.accounts.Account
|
2016-12-02 05:36:50 +01:00
|
|
|
import android.accounts.AccountAuthenticatorResponse
|
|
|
|
import android.accounts.AccountManager
|
2016-07-07 09:39:32 +02:00
|
|
|
import android.app.Activity
|
|
|
|
import android.app.Dialog
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.DialogInterface
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.res.ColorStateList
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.AsyncTask
|
2017-10-01 12:01:41 +02:00
|
|
|
import android.os.Build
|
2016-07-07 09:39:32 +02:00
|
|
|
import android.os.Bundle
|
2020-01-26 08:35:15 +01:00
|
|
|
import androidx.loader.app.LoaderManager
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
import androidx.loader.content.Loader
|
|
|
|
import androidx.collection.ArraySet
|
|
|
|
import androidx.core.view.ViewCompat
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
2016-07-07 09:39:32 +02:00
|
|
|
import android.text.Editable
|
|
|
|
import android.text.InputType
|
|
|
|
import android.text.TextWatcher
|
2017-04-18 11:56:59 +02:00
|
|
|
import android.view.*
|
2016-07-07 09:39:32 +02:00
|
|
|
import android.view.View.OnClickListener
|
2017-04-23 15:02:04 +02:00
|
|
|
import android.webkit.CookieManager
|
2017-05-02 04:14:00 +02:00
|
|
|
import android.webkit.CookieSyncManager
|
2017-06-19 15:45:41 +02:00
|
|
|
import android.widget.BaseExpandableListAdapter
|
|
|
|
import android.widget.ExpandableListView
|
|
|
|
import android.widget.TextView
|
|
|
|
import android.widget.Toast
|
2016-07-07 09:39:32 +02:00
|
|
|
import kotlinx.android.synthetic.main.activity_sign_in.*
|
2017-06-19 15:45:41 +02:00
|
|
|
import kotlinx.android.synthetic.main.dialog_expandable_list.*
|
|
|
|
import kotlinx.android.synthetic.main.dialog_login_verification_code.*
|
2017-08-28 11:50:04 +02:00
|
|
|
import nl.komponents.kovenant.CancelException
|
|
|
|
import nl.komponents.kovenant.Deferred
|
2017-03-22 13:00:29 +01:00
|
|
|
import nl.komponents.kovenant.combine.and
|
2017-08-28 11:50:04 +02:00
|
|
|
import nl.komponents.kovenant.deferred
|
2017-01-26 16:52:14 +01:00
|
|
|
import nl.komponents.kovenant.task
|
|
|
|
import nl.komponents.kovenant.ui.alwaysUi
|
2017-04-18 15:19:07 +02:00
|
|
|
import nl.komponents.kovenant.ui.failUi
|
2017-01-26 16:52:14 +01:00
|
|
|
import nl.komponents.kovenant.ui.successUi
|
2016-12-16 10:13:35 +01:00
|
|
|
import org.mariotaku.kpreferences.get
|
2017-01-02 09:33:27 +01:00
|
|
|
import org.mariotaku.ktextension.*
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.microblog.library.MicroBlog
|
|
|
|
import org.mariotaku.microblog.library.MicroBlogException
|
2017-04-18 15:19:07 +02:00
|
|
|
import org.mariotaku.microblog.library.mastodon.Mastodon
|
|
|
|
import org.mariotaku.microblog.library.mastodon.MastodonOAuth2
|
|
|
|
import org.mariotaku.microblog.library.mastodon.annotation.AuthScope
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.microblog.library.twitter.TwitterOAuth
|
|
|
|
import org.mariotaku.microblog.library.twitter.auth.BasicAuthorization
|
|
|
|
import org.mariotaku.microblog.library.twitter.auth.EmptyAuthorization
|
2020-04-03 11:14:42 +02:00
|
|
|
import org.mariotaku.microblog.library.twitter.model.Paging
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.microblog.library.twitter.model.User
|
|
|
|
import org.mariotaku.restfu.http.Endpoint
|
|
|
|
import org.mariotaku.restfu.oauth.OAuthToken
|
2017-04-18 17:10:44 +02:00
|
|
|
import org.mariotaku.restfu.oauth2.OAuth2Authorization
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.twidere.R
|
|
|
|
import org.mariotaku.twidere.TwidereConstants.*
|
2016-12-03 06:48:40 +01:00
|
|
|
import org.mariotaku.twidere.annotation.AccountType
|
2016-12-12 06:42:27 +01:00
|
|
|
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_API_CONFIG
|
2017-04-24 06:38:19 +02:00
|
|
|
import org.mariotaku.twidere.constant.apiLastChangeKey
|
2016-12-12 06:42:27 +01:00
|
|
|
import org.mariotaku.twidere.constant.defaultAPIConfigKey
|
2016-12-16 10:13:35 +01:00
|
|
|
import org.mariotaku.twidere.constant.randomizeAccountNameKey
|
2017-06-19 15:45:41 +02:00
|
|
|
import org.mariotaku.twidere.extension.*
|
2017-04-20 14:04:37 +02:00
|
|
|
import org.mariotaku.twidere.extension.model.*
|
2017-05-03 15:42:05 +02:00
|
|
|
import org.mariotaku.twidere.extension.model.api.isFanfouUser
|
|
|
|
import org.mariotaku.twidere.extension.model.api.key
|
2017-04-18 17:10:44 +02:00
|
|
|
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
2017-04-22 16:32:00 +02:00
|
|
|
import org.mariotaku.twidere.extension.model.api.toParcelable
|
2017-01-25 13:08:23 +01:00
|
|
|
import org.mariotaku.twidere.fragment.APIEditorDialogFragment
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
|
|
|
import org.mariotaku.twidere.fragment.ProgressDialogFragment
|
2017-04-18 11:56:59 +02:00
|
|
|
import org.mariotaku.twidere.loader.DefaultAPIConfigLoader
|
2016-12-12 06:42:27 +01:00
|
|
|
import org.mariotaku.twidere.model.CustomAPIConfig
|
2016-12-03 06:48:40 +01:00
|
|
|
import org.mariotaku.twidere.model.ParcelableUser
|
|
|
|
import org.mariotaku.twidere.model.SingleResponse
|
|
|
|
import org.mariotaku.twidere.model.UserKey
|
2016-12-15 06:11:32 +01:00
|
|
|
import org.mariotaku.twidere.model.account.AccountExtras
|
2017-04-19 12:59:39 +02:00
|
|
|
import org.mariotaku.twidere.model.account.MastodonAccountExtras
|
2016-12-03 06:48:40 +01:00
|
|
|
import org.mariotaku.twidere.model.account.StatusNetAccountExtras
|
|
|
|
import org.mariotaku.twidere.model.account.TwitterAccountExtras
|
2017-04-18 17:10:44 +02:00
|
|
|
import org.mariotaku.twidere.model.account.cred.*
|
2016-12-15 06:11:32 +01:00
|
|
|
import org.mariotaku.twidere.model.analyzer.SignIn
|
2016-12-04 04:58:03 +01:00
|
|
|
import org.mariotaku.twidere.model.util.AccountUtils
|
|
|
|
import org.mariotaku.twidere.model.util.ParcelableUserUtils
|
2016-07-07 09:39:32 +02:00
|
|
|
import org.mariotaku.twidere.util.*
|
|
|
|
import org.mariotaku.twidere.util.OAuthPasswordAuthenticator.*
|
2017-01-07 17:13:11 +01:00
|
|
|
import java.io.IOException
|
2016-07-07 09:39:32 +02:00
|
|
|
import java.lang.ref.WeakReference
|
2016-12-16 10:13:35 +01:00
|
|
|
import java.util.*
|
2017-04-24 06:38:19 +02:00
|
|
|
import kotlin.collections.ArrayList
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
|
2017-04-06 05:55:04 +02:00
|
|
|
class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
|
|
|
|
APIEditorDialogFragment.APIEditorCallback {
|
2017-04-18 11:56:59 +02:00
|
|
|
|
2016-12-12 06:42:27 +01:00
|
|
|
private lateinit var apiConfig: CustomAPIConfig
|
2016-07-07 09:39:32 +02:00
|
|
|
private var apiChangeTimestamp: Long = 0
|
|
|
|
private var signInTask: AbstractSignInTask? = null
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null
|
|
|
|
private var accountAuthenticatorResult: Bundle? = null
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
accountAuthenticatorResponse = intent.getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)
|
|
|
|
accountAuthenticatorResponse?.onRequestContinued()
|
|
|
|
|
|
|
|
setContentView(R.layout.activity_sign_in)
|
|
|
|
|
|
|
|
|
|
|
|
editUsername.addTextChangedListener(this)
|
|
|
|
editPassword.addTextChangedListener(this)
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
signIn.setOnClickListener(this)
|
|
|
|
signUp.setOnClickListener(this)
|
|
|
|
passwordSignIn.setOnClickListener(this)
|
|
|
|
|
|
|
|
val color = ColorStateList.valueOf(ContextCompat.getColor(this,
|
|
|
|
R.color.material_light_green))
|
|
|
|
ViewCompat.setBackgroundTintList(signIn, color)
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
|
|
|
|
if (savedInstanceState != null) {
|
2019-10-25 10:50:10 +02:00
|
|
|
apiConfig = savedInstanceState.getParcelable(EXTRA_API_CONFIG)!!
|
2017-04-18 11:56:59 +02:00
|
|
|
apiChangeTimestamp = savedInstanceState.getLong(EXTRA_API_LAST_CHANGE)
|
|
|
|
} else {
|
|
|
|
apiConfig = kPreferences[defaultAPIConfigKey]
|
|
|
|
}
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
updateSignInType()
|
|
|
|
setSignInButton()
|
2017-01-26 16:52:14 +01:00
|
|
|
|
|
|
|
if (savedInstanceState == null) {
|
|
|
|
// Only start at the first time
|
2017-05-18 16:26:45 +02:00
|
|
|
showLoginTypeChooser()
|
2017-10-01 12:01:41 +02:00
|
|
|
// Must call this before cookie manager under lollipop
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
CookieSyncManager.createInstance(this)
|
|
|
|
}
|
2017-04-23 15:02:04 +02:00
|
|
|
CookieManager.getInstance().removeAllCookiesSupport()
|
2017-01-26 16:52:14 +01:00
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
2016-12-29 06:50:18 +01:00
|
|
|
super.onCreateOptionsMenu(menu)
|
2016-12-02 05:36:50 +01:00
|
|
|
menuInflater.inflate(R.menu.menu_sign_in, menu)
|
|
|
|
return true
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
2016-07-07 09:39:32 +02:00
|
|
|
when (requestCode) {
|
|
|
|
REQUEST_EDIT_API -> {
|
2017-04-18 11:56:59 +02:00
|
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
apiConfig = data.getParcelableExtra(EXTRA_API_CONFIG)
|
2016-07-07 09:39:32 +02:00
|
|
|
updateSignInType()
|
|
|
|
}
|
|
|
|
setSignInButton()
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
2017-04-18 15:19:07 +02:00
|
|
|
REQUEST_BROWSER_TWITTER_SIGN_IN -> {
|
2016-07-07 09:39:32 +02:00
|
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
2017-01-27 15:35:28 +01:00
|
|
|
handleBrowserLoginResult(data)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
2017-04-18 15:19:07 +02:00
|
|
|
REQUEST_BROWSER_MASTODON_SIGN_IN -> {
|
|
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
2017-04-18 16:00:16 +02:00
|
|
|
val code = data.getStringExtra(EXTRA_CODE)
|
2019-10-25 10:50:10 +02:00
|
|
|
val extras = data.getBundleExtra(EXTRA_EXTRAS)!!
|
|
|
|
val host = extras.getString(EXTRA_HOST)!!
|
|
|
|
val clientId = extras.getString(EXTRA_CLIENT_ID)!!
|
|
|
|
val clientSecret = extras.getString(EXTRA_CLIENT_SECRET)!!
|
2017-04-18 16:00:16 +02:00
|
|
|
|
2017-04-18 15:19:07 +02:00
|
|
|
finishMastodonBrowserLogin(host, clientId, clientSecret, code)
|
|
|
|
}
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
}
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
override fun finish() {
|
|
|
|
accountAuthenticatorResponse?.let { response ->
|
|
|
|
// send the result bundle back if set, otherwise send an error.
|
|
|
|
if (accountAuthenticatorResult != null) {
|
|
|
|
response.onResult(accountAuthenticatorResult)
|
|
|
|
} else {
|
|
|
|
response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled")
|
|
|
|
}
|
|
|
|
accountAuthenticatorResponse = null
|
|
|
|
}
|
|
|
|
super.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun afterTextChanged(s: Editable) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-07-07 09:39:32 +02:00
|
|
|
override fun onClick(v: View) {
|
|
|
|
when (v) {
|
|
|
|
signUp -> {
|
2017-04-15 10:18:01 +02:00
|
|
|
val uri = apiConfig.signUpUrl?.let(Uri::parse) ?: return
|
|
|
|
OnLinkClickHandler.openLink(this, preferences, uri)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
signIn -> {
|
|
|
|
if (usernamePasswordContainer.visibility != View.VISIBLE) {
|
|
|
|
editUsername.text = null
|
|
|
|
editPassword.text = null
|
|
|
|
}
|
2017-04-24 06:38:19 +02:00
|
|
|
setDefaultAPI()
|
2017-04-18 15:19:07 +02:00
|
|
|
if (apiConfig.type == AccountType.MASTODON) {
|
|
|
|
performMastodonLogin()
|
|
|
|
} else when (apiConfig.credentialsType) {
|
2017-04-18 11:56:59 +02:00
|
|
|
Credentials.Type.OAUTH -> {
|
2017-04-18 15:19:07 +02:00
|
|
|
performBrowserLogin()
|
2017-04-18 11:56:59 +02:00
|
|
|
}
|
|
|
|
else -> {
|
2017-04-18 15:19:07 +02:00
|
|
|
val username = editUsername.text.toString()
|
|
|
|
val password = editPassword.text.toString()
|
|
|
|
performUserPassLogin(username, password)
|
2017-04-18 11:56:59 +02:00
|
|
|
}
|
2017-01-27 15:35:28 +01:00
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
passwordSignIn -> {
|
2017-01-19 12:43:20 +01:00
|
|
|
executeAfterFragmentResumed { fragment ->
|
2016-07-07 09:39:32 +02:00
|
|
|
val df = PasswordSignInDialogFragment()
|
2017-01-19 12:43:20 +01:00
|
|
|
df.show(fragment.supportFragmentManager, "password_sign_in")
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
|
|
when (item.itemId) {
|
|
|
|
android.R.id.home -> {
|
|
|
|
val accountKeys = DataStoreUtils.getActivatedAccountKeys(this)
|
2016-12-03 06:48:40 +01:00
|
|
|
if (accountKeys.isNotEmpty()) {
|
2016-07-07 09:39:32 +02:00
|
|
|
onBackPressed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
R.id.settings -> {
|
|
|
|
if (signInTask != null && signInTask!!.status == AsyncTask.Status.RUNNING)
|
|
|
|
return false
|
2017-03-13 06:35:11 +01:00
|
|
|
startActivity(IntentUtils.settings())
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
R.id.edit_api -> {
|
|
|
|
if (signInTask != null && signInTask!!.status == AsyncTask.Status.RUNNING)
|
|
|
|
return false
|
|
|
|
setDefaultAPI()
|
2017-01-25 13:08:23 +01:00
|
|
|
val df = APIEditorDialogFragment()
|
|
|
|
df.arguments = Bundle {
|
|
|
|
this[EXTRA_API_CONFIG] = apiConfig
|
|
|
|
this[APIEditorDialogFragment.EXTRA_SHOW_LOAD_DEFAULTS] = true
|
|
|
|
}
|
|
|
|
df.show(supportFragmentManager, "edit_api_config")
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return super.onOptionsItemSelected(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
2016-12-21 09:53:53 +01:00
|
|
|
super.onPrepareOptionsMenu(menu)
|
2016-07-07 09:39:32 +02:00
|
|
|
val itemBrowser = menu.findItem(R.id.open_in_browser)
|
|
|
|
if (itemBrowser != null) {
|
2016-12-12 06:42:27 +01:00
|
|
|
val is_oauth = apiConfig.credentialsType == Credentials.Type.OAUTH
|
2016-07-07 09:39:32 +02:00
|
|
|
itemBrowser.isVisible = is_oauth
|
|
|
|
itemBrowser.isEnabled = is_oauth
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-01-25 13:08:23 +01:00
|
|
|
override fun onSaveInstanceState(outState: Bundle) {
|
2016-12-12 06:42:27 +01:00
|
|
|
outState.putParcelable(EXTRA_API_CONFIG, apiConfig)
|
2016-07-07 09:39:32 +02:00
|
|
|
outState.putLong(EXTRA_API_LAST_CHANGE, apiChangeTimestamp)
|
|
|
|
super.onSaveInstanceState(outState)
|
|
|
|
}
|
|
|
|
|
2016-12-02 05:36:50 +01:00
|
|
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
2016-07-07 09:39:32 +02:00
|
|
|
setSignInButton()
|
|
|
|
}
|
|
|
|
|
2017-01-25 13:08:23 +01:00
|
|
|
override fun onSaveAPIConfig(config: CustomAPIConfig) {
|
|
|
|
apiConfig = config
|
|
|
|
|
|
|
|
updateSignInType()
|
|
|
|
setSignInButton()
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
2017-04-18 15:19:07 +02:00
|
|
|
|
|
|
|
private fun performBrowserLogin() {
|
|
|
|
val weakThis = WeakReference(this)
|
|
|
|
executeAfterFragmentResumed { activity ->
|
|
|
|
ProgressDialogFragment.show(activity.supportFragmentManager, "get_request_token")
|
|
|
|
} and task {
|
|
|
|
val activity = weakThis.get() ?: throw InterruptedException()
|
|
|
|
val apiConfig = activity.apiConfig
|
|
|
|
val apiUrlFormat = apiConfig.apiUrlFormat ?:
|
|
|
|
throw MicroBlogException("Invalid API URL format")
|
|
|
|
val endpoint = MicroBlogAPIFactory.getOAuthSignInEndpoint(apiUrlFormat,
|
|
|
|
apiConfig.isSameOAuthUrl)
|
|
|
|
val auth = apiConfig.getOAuthAuthorization() ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credentials")
|
|
|
|
val oauth = newMicroBlogInstance(activity, endpoint, auth, apiConfig.type,
|
|
|
|
TwitterOAuth::class.java)
|
|
|
|
return@task oauth.getRequestToken(OAUTH_CALLBACK_OOB)
|
|
|
|
}.successUi { requestToken ->
|
|
|
|
val activity = weakThis.get() ?: return@successUi
|
|
|
|
val intent = Intent(activity, BrowserSignInActivity::class.java)
|
|
|
|
val apiConfig = activity.apiConfig
|
|
|
|
val endpoint = MicroBlogAPIFactory.getOAuthSignInEndpoint(apiConfig.apiUrlFormat!!, true)
|
|
|
|
intent.data = Uri.parse(endpoint.construct("/oauth/authorize", arrayOf("oauth_token",
|
|
|
|
requestToken.oauthToken)))
|
|
|
|
intent.putExtra(EXTRA_EXTRAS, Bundle {
|
|
|
|
this[EXTRA_REQUEST_TOKEN] = requestToken.oauthToken
|
|
|
|
this[EXTRA_REQUEST_TOKEN_SECRET] = requestToken.oauthTokenSecret
|
|
|
|
})
|
|
|
|
activity.startActivityForResult(intent, REQUEST_BROWSER_TWITTER_SIGN_IN)
|
|
|
|
}.failUi {
|
|
|
|
val activity = weakThis.get() ?: return@failUi
|
|
|
|
// TODO show error message
|
2020-04-14 08:31:33 +02:00
|
|
|
if (it is MicroBlogException) {
|
|
|
|
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
|
|
|
|
}
|
2017-04-18 15:19:07 +02:00
|
|
|
}.alwaysUi {
|
|
|
|
executeAfterFragmentResumed {
|
|
|
|
it.supportFragmentManager.dismissDialogFragment("get_request_token")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun performMastodonLogin() {
|
|
|
|
val weakThis = WeakReference(this)
|
2017-04-18 16:00:16 +02:00
|
|
|
val host = editUsername.string?.takeIf(String::isNotEmpty) ?: run {
|
2017-04-18 17:10:44 +02:00
|
|
|
Toast.makeText(this, R.string.message_toast_invalid_mastodon_host,
|
2017-04-18 15:19:07 +02:00
|
|
|
Toast.LENGTH_SHORT).show()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val scopes = arrayOf(AuthScope.READ, AuthScope.WRITE, AuthScope.FOLLOW)
|
|
|
|
executeAfterFragmentResumed { activity ->
|
|
|
|
ProgressDialogFragment.show(activity.supportFragmentManager, "open_browser_auth")
|
|
|
|
} and task {
|
|
|
|
val activity = weakThis.get() ?: throw InterruptedException()
|
|
|
|
val registry = activity.mastodonApplicationRegistry
|
2017-04-24 10:56:51 +02:00
|
|
|
return@task Pair(host, registry[host] ?: registry.fetch(host, scopes))
|
2017-04-18 15:19:07 +02:00
|
|
|
}.successUi { (host, app) ->
|
|
|
|
val activity = weakThis.get() ?: return@successUi
|
|
|
|
val endpoint = Endpoint("https://$host/")
|
|
|
|
val intent = Intent(activity, BrowserSignInActivity::class.java)
|
|
|
|
intent.data = Uri.parse(endpoint.construct("/oauth/authorize",
|
|
|
|
arrayOf("response_type", "code"),
|
|
|
|
arrayOf("client_id", app.clientId),
|
|
|
|
arrayOf("redirect_uri", MASTODON_CALLBACK_URL),
|
|
|
|
arrayOf("scope", scopes.joinToString(" "))))
|
|
|
|
intent.putExtra(EXTRA_EXTRAS, Bundle {
|
|
|
|
this[EXTRA_HOST] = host
|
|
|
|
this[EXTRA_CLIENT_ID] = app.clientId
|
|
|
|
this[EXTRA_CLIENT_SECRET] = app.clientSecret
|
|
|
|
})
|
|
|
|
activity.startActivityForResult(intent, REQUEST_BROWSER_MASTODON_SIGN_IN)
|
|
|
|
}.failUi {
|
|
|
|
val activity = weakThis.get() ?: return@failUi
|
|
|
|
// TODO show error message
|
2017-04-23 10:00:05 +02:00
|
|
|
activity.onSignInError(it)
|
2017-04-18 15:19:07 +02:00
|
|
|
}.alwaysUi {
|
2017-04-23 10:00:05 +02:00
|
|
|
val activity = weakThis.get() ?: return@alwaysUi
|
|
|
|
activity.executeAfterFragmentResumed {
|
2017-04-18 15:19:07 +02:00
|
|
|
it.supportFragmentManager.dismissDialogFragment("open_browser_auth")
|
|
|
|
}
|
|
|
|
}
|
2017-01-25 13:08:23 +01:00
|
|
|
}
|
|
|
|
|
2017-04-18 15:19:07 +02:00
|
|
|
private fun performUserPassLogin(username: String, password: String) {
|
2016-07-07 09:39:32 +02:00
|
|
|
if (signInTask != null && signInTask!!.status == AsyncTask.Status.RUNNING) {
|
|
|
|
signInTask!!.cancel(true)
|
|
|
|
}
|
2016-12-12 06:42:27 +01:00
|
|
|
|
2017-05-19 17:08:23 +02:00
|
|
|
signInTask = SignInTask(this, username, password, apiConfig).apply { execute() }
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
private fun onSignInResult(result: SignInResponse) {
|
2017-01-25 13:08:23 +01:00
|
|
|
val am = AccountManager.get(this)
|
|
|
|
setSignInButton()
|
|
|
|
if (result.alreadyLoggedIn) {
|
|
|
|
result.updateAccount(am)
|
2017-04-06 17:27:47 +02:00
|
|
|
contentResolver.deleteAccountData(result.user.key)
|
2017-01-26 14:28:43 +01:00
|
|
|
Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show()
|
2017-01-25 13:08:23 +01:00
|
|
|
} else {
|
|
|
|
result.addAccount(am, preferences[randomizeAccountNameKey])
|
2017-04-18 17:10:44 +02:00
|
|
|
Analyzer.log(SignIn(true, accountType = result.type,
|
|
|
|
credentialsType = apiConfig.credentialsType,
|
2020-04-03 11:14:42 +02:00
|
|
|
officialKey = result.extras?.official == true))
|
2017-01-25 13:08:23 +01:00
|
|
|
finishSignIn()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
private fun dismissDialogFragment(tag: String) {
|
2017-01-25 13:08:23 +01:00
|
|
|
executeAfterFragmentResumed {
|
2017-04-24 10:56:51 +02:00
|
|
|
it.supportFragmentManager.dismissDialogFragment(tag)
|
2017-01-25 13:08:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun onSignInError(exception: Exception) {
|
2017-01-26 16:15:05 +01:00
|
|
|
DebugLog.w(LOGTAG, "Sign in error", exception)
|
2017-01-25 13:08:23 +01:00
|
|
|
var errorReason: String? = null
|
2020-06-09 02:21:48 +02:00
|
|
|
when (exception) {
|
|
|
|
is AuthenticityTokenException -> {
|
|
|
|
Toast.makeText(this, R.string.message_toast_wrong_api_key, Toast.LENGTH_SHORT).show()
|
|
|
|
errorReason = "wrong_api_key"
|
|
|
|
}
|
|
|
|
is WrongUserPassException -> {
|
|
|
|
Toast.makeText(this, R.string.message_toast_wrong_username_password, Toast.LENGTH_SHORT).show()
|
|
|
|
errorReason = "wrong_username_password"
|
|
|
|
}
|
|
|
|
is SignInTask.WrongBasicCredentialException -> {
|
|
|
|
Toast.makeText(this, R.string.message_toast_wrong_username_password, Toast.LENGTH_SHORT).show()
|
|
|
|
errorReason = "wrong_username_password"
|
|
|
|
}
|
|
|
|
is SignInTask.WrongAPIURLFormatException -> {
|
|
|
|
Toast.makeText(this, R.string.message_toast_wrong_api_key, Toast.LENGTH_SHORT).show()
|
|
|
|
errorReason = "wrong_api_key"
|
|
|
|
}
|
|
|
|
is LoginVerificationException -> {
|
|
|
|
Toast.makeText(this, R.string.message_toast_login_verification_failed, Toast.LENGTH_SHORT).show()
|
|
|
|
errorReason = "login_verification_failed"
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
Toast.makeText(this, exception.getErrorMessage(this), Toast.LENGTH_SHORT).show()
|
|
|
|
}
|
2017-01-25 13:08:23 +01:00
|
|
|
}
|
|
|
|
Analyzer.log(SignIn(false, credentialsType = apiConfig.credentialsType,
|
|
|
|
errorReason = errorReason, accountType = apiConfig.type))
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
|
2017-01-25 13:08:23 +01:00
|
|
|
internal fun onSignInStart() {
|
|
|
|
showSignInProgressDialog()
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun showSignInProgressDialog() {
|
|
|
|
executeAfterFragmentResumed {
|
|
|
|
if (isFinishing) return@executeAfterFragmentResumed
|
|
|
|
val fm = supportFragmentManager
|
|
|
|
val ft = fm.beginTransaction()
|
|
|
|
val fragment = ProgressDialogFragment()
|
|
|
|
fragment.isCancelable = false
|
|
|
|
fragment.show(ft, FRAGMENT_TAG_SIGN_IN_PROGRESS)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-28 11:50:04 +02:00
|
|
|
internal fun dismissSignInProgressDialog() {
|
|
|
|
dismissDialogFragment(FRAGMENT_TAG_SIGN_IN_PROGRESS)
|
|
|
|
}
|
|
|
|
|
2017-05-18 16:26:45 +02:00
|
|
|
private fun showLoginTypeChooser() {
|
2017-03-22 13:00:29 +01:00
|
|
|
executeAfterFragmentResumed {
|
2017-05-18 16:26:45 +02:00
|
|
|
val fm = it.supportFragmentManager
|
2017-06-13 06:05:36 +02:00
|
|
|
val df = SignInTypeChooserDialogFragment()
|
2017-05-18 16:26:45 +02:00
|
|
|
df.show(fm, "login_type_chooser")
|
2017-01-26 16:52:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
|
|
|
|
private fun finishMastodonBrowserLogin(host: String, clientId: String, clientSecret: String, code: String) {
|
2017-05-19 17:08:23 +02:00
|
|
|
signInTask = MastodonLoginTask(this, host, clientId, clientSecret, code).apply { execute() }
|
2017-04-18 17:10:44 +02:00
|
|
|
}
|
|
|
|
|
2017-01-27 15:35:28 +01:00
|
|
|
private fun handleBrowserLoginResult(intent: Intent?) {
|
2016-07-07 09:39:32 +02:00
|
|
|
if (intent == null) return
|
2017-04-22 16:32:00 +02:00
|
|
|
val extras = intent.getBundleExtra(EXTRA_EXTRAS) ?: return
|
|
|
|
val requestToken = OAuthToken(extras.getString(EXTRA_REQUEST_TOKEN),
|
|
|
|
extras.getString(EXTRA_REQUEST_TOKEN_SECRET))
|
2017-05-13 12:59:13 +02:00
|
|
|
val verifier = intent.getStringExtra(EXTRA_OAUTH_VERIFIER)
|
2017-05-19 17:08:23 +02:00
|
|
|
signInTask = BrowserSignInTask(this, apiConfig, requestToken, verifier).apply { execute() }
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun setDefaultAPI() {
|
2017-04-24 06:38:19 +02:00
|
|
|
if (!apiConfig.isDefault) return
|
|
|
|
val apiLastChange = preferences[apiLastChangeKey]
|
|
|
|
if (apiLastChange != apiChangeTimestamp) {
|
|
|
|
apiConfig = preferences[defaultAPIConfigKey]
|
2016-07-07 09:39:32 +02:00
|
|
|
apiChangeTimestamp = apiLastChange
|
|
|
|
}
|
2017-04-18 11:56:59 +02:00
|
|
|
updateSignInType()
|
|
|
|
setSignInButton()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateSignInType() {
|
|
|
|
// Mastodon have different case
|
|
|
|
if (apiConfig.type == AccountType.MASTODON) {
|
|
|
|
usernamePasswordContainer.visibility = View.VISIBLE
|
|
|
|
editPassword.visibility = View.GONE
|
2017-04-18 16:00:16 +02:00
|
|
|
editUsername.hint = getString(R.string.label_mastodon_host)
|
2017-04-18 11:56:59 +02:00
|
|
|
} else when (apiConfig.credentialsType) {
|
|
|
|
Credentials.Type.XAUTH, Credentials.Type.BASIC -> {
|
|
|
|
usernamePasswordContainer.visibility = View.VISIBLE
|
|
|
|
editPassword.visibility = View.VISIBLE
|
|
|
|
editUsername.hint = getString(R.string.label_username)
|
|
|
|
}
|
|
|
|
Credentials.Type.EMPTY -> {
|
|
|
|
usernamePasswordContainer.visibility = View.GONE
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
usernamePasswordContainer.visibility = View.GONE
|
|
|
|
}
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun setSignInButton() {
|
2017-04-18 11:56:59 +02:00
|
|
|
// Mastodon have different case
|
|
|
|
if (apiConfig.type == AccountType.MASTODON) {
|
|
|
|
passwordSignIn.visibility = View.GONE
|
|
|
|
signIn.isEnabled = true
|
|
|
|
} else when (apiConfig.credentialsType) {
|
2016-12-03 06:48:40 +01:00
|
|
|
Credentials.Type.XAUTH, Credentials.Type.BASIC -> {
|
2016-07-07 09:39:32 +02:00
|
|
|
passwordSignIn.visibility = View.GONE
|
2020-01-26 08:35:15 +01:00
|
|
|
signIn.isEnabled = !(editPassword.text.isNullOrEmpty() || editUsername.text.isNullOrEmpty())
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2016-12-03 06:48:40 +01:00
|
|
|
Credentials.Type.OAUTH -> {
|
2016-07-07 09:39:32 +02:00
|
|
|
passwordSignIn.visibility = View.VISIBLE
|
|
|
|
signIn.isEnabled = true
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
passwordSignIn.visibility = View.GONE
|
|
|
|
signIn.isEnabled = true
|
|
|
|
}
|
|
|
|
}
|
2017-01-26 16:52:14 +01:00
|
|
|
signUp.visibility = if (apiConfig.signUpUrlOrDefault != null) {
|
2017-01-19 12:43:20 +01:00
|
|
|
View.VISIBLE
|
|
|
|
} else {
|
|
|
|
View.GONE
|
|
|
|
}
|
|
|
|
passwordSignIn.visibility = if (apiConfig.type == null || apiConfig.type == AccountType.TWITTER) {
|
|
|
|
View.VISIBLE
|
|
|
|
} else {
|
|
|
|
View.GONE
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2016-12-17 05:10:24 +01:00
|
|
|
|
|
|
|
private fun finishSignIn() {
|
|
|
|
if (accountAuthenticatorResponse != null) {
|
|
|
|
accountAuthenticatorResult = Bundle {
|
|
|
|
this[AccountManager.KEY_BOOLEAN_RESULT] = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val intent = Intent(this, HomeActivity::class.java)
|
2017-01-07 17:13:11 +01:00
|
|
|
//TODO refresh timelines
|
2016-12-17 05:10:24 +01:00
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
|
|
startActivity(intent)
|
2016-12-03 06:48:40 +01:00
|
|
|
}
|
2016-12-17 05:10:24 +01:00
|
|
|
finish()
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-06-13 06:05:36 +02:00
|
|
|
class SignInTypeChooserDialogFragment : BaseDialogFragment(),
|
2017-04-18 11:56:59 +02:00
|
|
|
LoaderManager.LoaderCallbacks<List<CustomAPIConfig>> {
|
|
|
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
2020-05-31 07:54:57 +02:00
|
|
|
val builder = AlertDialog.Builder(requireContext())
|
2017-04-18 11:56:59 +02:00
|
|
|
builder.setView(R.layout.dialog_expandable_list)
|
|
|
|
val dialog = builder.create()
|
2017-09-03 15:23:45 +02:00
|
|
|
dialog.onShow {
|
|
|
|
it.applyTheme()
|
|
|
|
val listView = it.expandableList
|
2020-05-31 07:54:57 +02:00
|
|
|
val adapter = LoginTypeAdapter(requireContext())
|
2017-04-18 11:56:59 +02:00
|
|
|
listView.setAdapter(adapter)
|
|
|
|
listView.setOnGroupClickListener { _, _, groupPosition, _ ->
|
|
|
|
val type = adapter.getGroup(groupPosition)
|
|
|
|
if (type.hasChildren) return@setOnGroupClickListener false
|
|
|
|
val activity = activity as? SignInActivity
|
|
|
|
val config = type.configs.single()
|
|
|
|
activity?.let {
|
|
|
|
it.apiConfig = config
|
|
|
|
it.updateSignInType()
|
|
|
|
it.setSignInButton()
|
|
|
|
}
|
|
|
|
dismiss()
|
|
|
|
return@setOnGroupClickListener true
|
|
|
|
}
|
|
|
|
listView.setOnChildClickListener { _, _, groupPosition, childPosition, _ ->
|
|
|
|
val config = adapter.getChild(groupPosition, childPosition)
|
|
|
|
val activity = activity as? SignInActivity
|
|
|
|
activity?.let {
|
|
|
|
it.apiConfig = config
|
|
|
|
it.updateSignInType()
|
|
|
|
it.setSignInButton()
|
|
|
|
}
|
|
|
|
dismiss()
|
|
|
|
return@setOnChildClickListener true
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
loaderManager.initLoader(0, null, this)
|
2016-12-03 06:48:40 +01:00
|
|
|
}
|
2017-04-18 11:56:59 +02:00
|
|
|
return dialog
|
2016-12-03 06:48:40 +01:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun onLoadFinished(loader: Loader<List<CustomAPIConfig>>, data: List<CustomAPIConfig>) {
|
|
|
|
val dialog = dialog ?: return
|
2017-06-19 06:11:28 +02:00
|
|
|
val listView: ExpandableListView = dialog.findViewById(R.id.expandableList)
|
2017-04-24 06:38:19 +02:00
|
|
|
val defaultConfig = preferences[defaultAPIConfigKey]
|
2017-05-21 04:11:37 +02:00
|
|
|
defaultConfig.name = getString(R.string.login_type_user_settings)
|
2017-05-03 15:42:05 +02:00
|
|
|
val allConfig = ArraySet(data)
|
|
|
|
allConfig.add(defaultConfig)
|
|
|
|
val configGroup = allConfig.groupBy { it.safeType }
|
2017-04-22 09:28:12 +02:00
|
|
|
val supportedAccountTypes = arrayOf(AccountType.TWITTER, AccountType.FANFOU,
|
|
|
|
AccountType.MASTODON, AccountType.STATUSNET)
|
2017-04-24 06:38:19 +02:00
|
|
|
val result = supportedAccountTypes.mapNotNullTo(ArrayList()) { type ->
|
|
|
|
if (type == AccountType.MASTODON) return@mapNotNullTo LoginType(type,
|
2020-05-31 07:54:57 +02:00
|
|
|
listOf(CustomAPIConfig.mastodon(requireContext())))
|
2017-05-03 15:42:05 +02:00
|
|
|
return@mapNotNullTo configGroup[type]?.let { list ->
|
|
|
|
LoginType(type, list.sortedBy { !it.isDefault })
|
|
|
|
}
|
2017-04-24 06:38:19 +02:00
|
|
|
}
|
|
|
|
(listView.expandableListAdapter as LoginTypeAdapter).data = result
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<CustomAPIConfig>> {
|
2020-05-31 07:54:57 +02:00
|
|
|
return DefaultAPIConfigLoader(requireContext())
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun onLoaderReset(loader: Loader<List<CustomAPIConfig>>) {
|
|
|
|
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
private data class LoginType(val type: String, val configs: List<CustomAPIConfig>) {
|
|
|
|
val hasChildren = configs.size > 1
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
private class LoginTypeAdapter(val context: Context) : BaseExpandableListAdapter() {
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
private val inflater = LayoutInflater.from(context)
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
var data: List<LoginType>? = null
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun getGroupCount() = data?.count() ?: 0
|
|
|
|
override fun getGroup(groupPosition: Int) = data!![groupPosition]
|
|
|
|
override fun getChild(groupPosition: Int, childPosition: Int) =
|
|
|
|
getGroup(groupPosition).configs[childPosition]
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun getChildrenCount(groupPosition: Int): Int {
|
|
|
|
val size = getGroup(groupPosition).configs.size
|
|
|
|
if (size > 1) return size
|
|
|
|
return 0
|
2016-12-03 06:48:40 +01:00
|
|
|
}
|
2016-12-08 16:45:07 +01:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun isChildSelectable(groupPosition: Int, childPosition: Int) = true
|
|
|
|
override fun hasStableIds() = false
|
|
|
|
override fun getGroupId(groupPosition: Int) = groupPosition.toLong()
|
|
|
|
override fun getChildId(groupPosition: Int, childPosition: Int): Long =
|
|
|
|
groupPosition.toLong().shl(32) or childPosition.toLong()
|
2016-12-03 06:48:40 +01:00
|
|
|
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
override fun getGroupView(groupPosition: Int, isExpanded: Boolean, convertView: View?,
|
|
|
|
parent: ViewGroup): View {
|
|
|
|
val view = convertView ?: inflater.inflate(android.R.layout.simple_expandable_list_item_1, parent, false)
|
2017-06-19 06:11:28 +02:00
|
|
|
val text1 = view.findViewById<TextView>(android.R.id.text1)
|
2017-04-24 06:38:19 +02:00
|
|
|
val group = getGroup(groupPosition)
|
2017-05-03 15:42:05 +02:00
|
|
|
text1.text = APIEditorDialogFragment.getTypeTitle(context, group.type)
|
2017-04-18 11:56:59 +02:00
|
|
|
return view
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun getChildView(groupPosition: Int, childPosition: Int, isLastChild: Boolean,
|
|
|
|
convertView: View?, parent: ViewGroup): View {
|
|
|
|
val view = convertView ?: inflater.inflate(android.R.layout.simple_list_item_1, parent, false)
|
|
|
|
val config = getChild(groupPosition, childPosition)
|
2017-06-19 06:11:28 +02:00
|
|
|
val text1 = view.findViewById<TextView>(android.R.id.text1)
|
2017-04-18 11:56:59 +02:00
|
|
|
text1.text = config.name
|
|
|
|
return view
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-19 15:45:41 +02:00
|
|
|
internal class InputLoginVerificationDialogFragment : BaseDialogFragment() {
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-08-28 11:50:04 +02:00
|
|
|
var deferred: Deferred<String?, Exception>? = null
|
2016-07-12 03:22:33 +02:00
|
|
|
var challengeType: String? = null
|
2016-07-07 09:39:32 +02:00
|
|
|
|
|
|
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
2020-05-31 07:54:57 +02:00
|
|
|
val builder = AlertDialog.Builder(requireContext())
|
2016-07-07 09:39:32 +02:00
|
|
|
builder.setTitle(R.string.login_verification)
|
|
|
|
builder.setView(R.layout.dialog_login_verification_code)
|
2017-06-19 15:45:41 +02:00
|
|
|
builder.positive(android.R.string.ok, this::performVerification)
|
|
|
|
builder.negative(android.R.string.cancel, this::cancelVerification)
|
2016-07-07 09:39:32 +02:00
|
|
|
val dialog = builder.create()
|
2017-06-19 15:45:41 +02:00
|
|
|
dialog.onShow(this::onDialogShow)
|
2016-07-07 09:39:32 +02:00
|
|
|
return dialog
|
|
|
|
}
|
|
|
|
|
2020-01-26 08:35:15 +01:00
|
|
|
override fun onCancel(dialog: DialogInterface) {
|
2017-08-28 11:50:04 +02:00
|
|
|
deferred?.reject(CancelException())
|
2017-06-19 15:45:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun performVerification(dialog: Dialog) {
|
2017-08-28 11:50:04 +02:00
|
|
|
deferred?.resolve(dialog.editVerificationCode.string)
|
2017-06-19 15:45:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun cancelVerification(dialog: Dialog) {
|
2017-08-28 11:50:04 +02:00
|
|
|
deferred?.reject(CancelException())
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-06-19 15:45:41 +02:00
|
|
|
private fun onDialogShow(dialog: Dialog) {
|
|
|
|
(dialog as? AlertDialog)?.applyTheme()
|
|
|
|
val verificationHint = dialog.verificationHint
|
|
|
|
val editVerification = dialog.editVerificationCode
|
2016-07-07 09:39:32 +02:00
|
|
|
if (verificationHint == null || editVerification == null) return
|
2016-07-12 03:22:33 +02:00
|
|
|
when {
|
|
|
|
"Push".equals(challengeType, ignoreCase = true) -> {
|
|
|
|
verificationHint.setText(R.string.login_verification_push_hint)
|
|
|
|
editVerification.visibility = View.GONE
|
|
|
|
}
|
|
|
|
"RetypePhoneNumber".equals(challengeType, ignoreCase = true) -> {
|
|
|
|
verificationHint.setText(R.string.login_challenge_retype_phone_hint)
|
|
|
|
editVerification.inputType = InputType.TYPE_CLASS_PHONE
|
|
|
|
editVerification.visibility = View.VISIBLE
|
|
|
|
}
|
|
|
|
"RetypeEmail".equals(challengeType, ignoreCase = true) -> {
|
|
|
|
verificationHint.setText(R.string.login_challenge_retype_email_hint)
|
|
|
|
editVerification.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
|
|
editVerification.visibility = View.VISIBLE
|
|
|
|
}
|
|
|
|
"Sms".equals(challengeType, ignoreCase = true) -> {
|
|
|
|
verificationHint.setText(R.string.login_verification_pin_hint)
|
|
|
|
editVerification.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
|
|
editVerification.visibility = View.VISIBLE
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
verificationHint.text = getString(R.string.unsupported_login_verification_type_name,
|
|
|
|
challengeType)
|
|
|
|
editVerification.visibility = View.VISIBLE
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PasswordSignInDialogFragment : BaseDialogFragment() {
|
2017-04-18 11:56:59 +02:00
|
|
|
|
2016-07-07 09:39:32 +02:00
|
|
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
2020-05-31 07:54:57 +02:00
|
|
|
val builder = AlertDialog.Builder(requireContext())
|
2016-07-07 09:39:32 +02:00
|
|
|
builder.setView(R.layout.dialog_password_sign_in)
|
2017-06-19 15:45:41 +02:00
|
|
|
builder.positive(R.string.action_sign_in, this::onPositiveButton)
|
2016-07-07 09:39:32 +02:00
|
|
|
builder.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
|
|
|
val alertDialog = builder.create()
|
2017-06-19 15:45:41 +02:00
|
|
|
alertDialog.onShow { dialog ->
|
|
|
|
dialog.applyTheme()
|
|
|
|
val editUsername = dialog.editUsername
|
|
|
|
val editPassword = dialog.editPassword
|
2016-07-07 09:39:32 +02:00
|
|
|
val textWatcher = object : TextWatcher {
|
|
|
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
2017-06-19 15:45:41 +02:00
|
|
|
val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE) ?: return
|
2017-04-18 11:56:59 +02:00
|
|
|
button.isEnabled = editUsername.length() > 0 && editPassword.length() > 0
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun afterTextChanged(s: Editable) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
editUsername.addTextChangedListener(textWatcher)
|
|
|
|
editPassword.addTextChangedListener(textWatcher)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
return alertDialog
|
|
|
|
}
|
2017-06-19 15:45:41 +02:00
|
|
|
|
|
|
|
private fun onPositiveButton(dialog: Dialog) {
|
|
|
|
val activity = activity as SignInActivity
|
|
|
|
val username = dialog.editUsername.string.orEmpty()
|
|
|
|
val password = dialog.editPassword.string.orEmpty()
|
|
|
|
activity.setDefaultAPI()
|
|
|
|
activity.performUserPassLogin(username, password)
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
internal class BrowserSignInTask(activity: SignInActivity, private val apiConfig: CustomAPIConfig,
|
2017-04-18 15:19:07 +02:00
|
|
|
private val requestToken: OAuthToken, private val oauthVerifier: String?) :
|
2017-04-18 17:10:44 +02:00
|
|
|
AbstractSignInTask(activity) {
|
2017-04-18 11:56:59 +02:00
|
|
|
|
|
|
|
@Throws(Exception::class)
|
|
|
|
override fun performLogin(): SignInResponse {
|
2017-04-18 17:10:44 +02:00
|
|
|
val context = activityRef.get() ?: throw InterruptedException()
|
2017-04-18 11:56:59 +02:00
|
|
|
val versionSuffix = if (apiConfig.isNoVersionSuffix) null else "1.1"
|
|
|
|
val apiUrlFormat = apiConfig.apiUrlFormat ?: throw MicroBlogException("No API URL format")
|
|
|
|
var auth = apiConfig.getOAuthAuthorization() ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
|
|
|
var endpoint = MicroBlogAPIFactory.getOAuthSignInEndpoint(apiUrlFormat,
|
|
|
|
apiConfig.isSameOAuthUrl)
|
|
|
|
val oauth = newMicroBlogInstance(context, endpoint = endpoint, auth = auth,
|
|
|
|
accountType = apiConfig.type, cls = TwitterOAuth::class.java)
|
|
|
|
val accessToken: OAuthToken
|
2020-06-08 23:19:10 +02:00
|
|
|
accessToken = if (oauthVerifier != null) {
|
|
|
|
oauth.getAccessToken(requestToken, oauthVerifier)
|
2017-04-18 11:56:59 +02:00
|
|
|
} else {
|
2020-06-08 23:19:10 +02:00
|
|
|
oauth.getAccessToken(requestToken)
|
2017-04-18 11:56:59 +02:00
|
|
|
}
|
|
|
|
auth = apiConfig.getOAuthAuthorization(accessToken) ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
|
|
|
endpoint = MicroBlogAPIFactory.getOAuthEndpoint(apiUrlFormat, "api", versionSuffix,
|
|
|
|
apiConfig.isSameOAuthUrl)
|
|
|
|
|
|
|
|
val twitter = newMicroBlogInstance(context, endpoint = endpoint, auth = auth,
|
|
|
|
accountType = apiConfig.type, cls = MicroBlog::class.java)
|
|
|
|
val apiUser = twitter.verifyCredentials()
|
|
|
|
var color = analyseUserProfileColor(apiUser)
|
2020-06-08 23:01:17 +02:00
|
|
|
val (type, extras) = detectAccountType(twitter, apiUser, apiConfig.type)
|
2017-05-03 15:42:05 +02:00
|
|
|
val accountKey = apiUser.key
|
2017-04-20 19:23:11 +02:00
|
|
|
val user = apiUser.toParcelable(accountKey, type, profileImageSize = profileImageSize)
|
2017-04-18 11:56:59 +02:00
|
|
|
val am = AccountManager.get(context)
|
|
|
|
val account = AccountUtils.findByAccountKey(am, accountKey)
|
|
|
|
if (account != null) {
|
|
|
|
color = account.getColor(am)
|
|
|
|
}
|
|
|
|
val credentials = OAuthCredentials()
|
|
|
|
credentials.api_url_format = apiUrlFormat
|
|
|
|
credentials.no_version_suffix = apiConfig.isNoVersionSuffix
|
|
|
|
|
|
|
|
credentials.same_oauth_signing_url = apiConfig.isSameOAuthUrl
|
|
|
|
|
|
|
|
credentials.consumer_key = auth.consumerKey
|
|
|
|
credentials.consumer_secret = auth.consumerSecret
|
|
|
|
credentials.access_token = accessToken.oauthToken
|
|
|
|
credentials.access_token_secret = accessToken.oauthTokenSecret
|
|
|
|
|
|
|
|
return SignInResponse(account != null, Credentials.Type.OAUTH, credentials, user, color,
|
2017-04-18 17:10:44 +02:00
|
|
|
type, extras)
|
2017-04-18 11:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
internal class MastodonLoginTask(context: SignInActivity, val host: String, val clientId: String,
|
|
|
|
val clientSecret: String, val code: String) : AbstractSignInTask(context) {
|
|
|
|
@Throws(Exception::class)
|
|
|
|
override fun performLogin(): SignInResponse {
|
|
|
|
val context = activityRef.get() ?: throw InterruptedException()
|
|
|
|
val oauth2 = newMicroBlogInstance(context, Endpoint("https://$host/"),
|
|
|
|
EmptyAuthorization(), AccountType.MASTODON, MastodonOAuth2::class.java)
|
|
|
|
val token = oauth2.getToken(clientId, clientSecret, code, MASTODON_CALLBACK_URL)
|
|
|
|
|
|
|
|
val endpoint = Endpoint("https://$host/api/")
|
|
|
|
val auth = OAuth2Authorization(token.accessToken)
|
|
|
|
val mastodon = newMicroBlogInstance(context, endpoint = endpoint, auth = auth,
|
|
|
|
accountType = AccountType.MASTODON, cls = Mastodon::class.java)
|
|
|
|
val apiAccount = mastodon.verifyCredentials()
|
|
|
|
var color = 0
|
|
|
|
val accountKey = UserKey(apiAccount.id, host)
|
|
|
|
val user = apiAccount.toParcelable(accountKey)
|
|
|
|
val am = AccountManager.get(context)
|
|
|
|
val account = AccountUtils.findByAccountKey(am, accountKey)
|
|
|
|
if (account != null) {
|
|
|
|
color = account.getColor(am)
|
|
|
|
}
|
|
|
|
val credentials = OAuth2Credentials()
|
|
|
|
credentials.api_url_format = endpoint.url
|
|
|
|
credentials.no_version_suffix = true
|
|
|
|
|
|
|
|
credentials.access_token = token.accessToken
|
|
|
|
return SignInResponse(account != null, Credentials.Type.OAUTH2, credentials, user,
|
2017-04-19 12:59:39 +02:00
|
|
|
color, AccountType.MASTODON, getMastodonAccountExtras(mastodon))
|
2017-04-18 17:10:44 +02:00
|
|
|
}
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2016-12-03 06:48:40 +01:00
|
|
|
internal class SignInTask(
|
|
|
|
activity: SignInActivity,
|
|
|
|
private val username: String,
|
|
|
|
private val password: String,
|
2017-01-07 17:13:11 +01:00
|
|
|
private val apiConfig: CustomAPIConfig
|
2016-12-03 06:48:40 +01:00
|
|
|
) : AbstractSignInTask(activity) {
|
2017-01-07 17:13:11 +01:00
|
|
|
private val verificationCallback = InputLoginVerificationCallback()
|
|
|
|
private val userAgent = UserAgentUtils.getDefaultUserAgentString(activity)
|
|
|
|
private val apiUrlFormat = apiConfig.apiUrlFormat ?: DEFAULT_TWITTER_API_URL_FORMAT
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2016-12-03 06:48:40 +01:00
|
|
|
@Throws(Exception::class)
|
|
|
|
override fun performLogin(): SignInResponse {
|
2017-01-07 17:13:11 +01:00
|
|
|
when (apiConfig.credentialsType) {
|
2016-12-03 06:48:40 +01:00
|
|
|
Credentials.Type.OAUTH -> return authOAuth()
|
|
|
|
Credentials.Type.XAUTH -> return authxAuth()
|
|
|
|
Credentials.Type.BASIC -> return authBasic()
|
|
|
|
Credentials.Type.EMPTY -> return authTwipOMode()
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2016-12-03 06:48:40 +01:00
|
|
|
return authOAuth()
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 23:01:17 +02:00
|
|
|
@Throws(AuthenticationException::class, MicroBlogException::class)
|
2016-07-07 09:39:32 +02:00
|
|
|
private fun authOAuth(): SignInResponse {
|
2016-12-03 06:48:40 +01:00
|
|
|
val activity = activityRef.get() ?: throw InterruptedException()
|
2016-07-07 09:39:32 +02:00
|
|
|
val endpoint = MicroBlogAPIFactory.getOAuthSignInEndpoint(apiUrlFormat,
|
2017-01-07 17:13:11 +01:00
|
|
|
apiConfig.isSameOAuthUrl)
|
2017-04-18 11:56:59 +02:00
|
|
|
val auth = apiConfig.getOAuthAuthorization() ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
2016-12-08 16:45:07 +01:00
|
|
|
val oauth = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = TwitterOAuth::class.java)
|
2016-07-07 09:39:32 +02:00
|
|
|
val authenticator = OAuthPasswordAuthenticator(oauth,
|
|
|
|
verificationCallback, userAgent)
|
|
|
|
val accessToken = authenticator.getOAuthAccessToken(username, password)
|
2017-04-12 14:58:08 +02:00
|
|
|
val userId = accessToken.userId
|
2017-09-03 15:23:45 +02:00
|
|
|
return getOAuthSignInResponse(activity, accessToken, Credentials.Type.OAUTH)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(MicroBlogException::class)
|
|
|
|
private fun authxAuth(): SignInResponse {
|
2016-12-03 06:48:40 +01:00
|
|
|
val activity = activityRef.get() ?: throw InterruptedException()
|
2016-07-07 09:39:32 +02:00
|
|
|
var endpoint = MicroBlogAPIFactory.getOAuthSignInEndpoint(apiUrlFormat,
|
2017-01-07 17:13:11 +01:00
|
|
|
apiConfig.isSameOAuthUrl)
|
2017-04-18 11:56:59 +02:00
|
|
|
var auth = apiConfig.getOAuthAuthorization() ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
2016-12-08 16:45:07 +01:00
|
|
|
val oauth = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = TwitterOAuth::class.java)
|
2016-07-07 09:39:32 +02:00
|
|
|
val accessToken = oauth.getAccessToken(username, password)
|
2017-04-12 14:58:08 +02:00
|
|
|
val userId = accessToken.userId ?: run {
|
2016-07-07 09:39:32 +02:00
|
|
|
// Trying to fix up userId if accessToken doesn't contain one.
|
2017-04-18 11:56:59 +02:00
|
|
|
auth = apiConfig.getOAuthAuthorization(accessToken) ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
|
|
|
endpoint = MicroBlogAPIFactory.getOAuthRestEndpoint(apiUrlFormat,
|
|
|
|
apiConfig.isSameOAuthUrl, apiConfig.isNoVersionSuffix)
|
2016-12-08 16:45:07 +01:00
|
|
|
val microBlog = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = MicroBlog::class.java)
|
2017-04-12 14:58:08 +02:00
|
|
|
return@run microBlog.verifyCredentials().id
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2017-09-03 15:23:45 +02:00
|
|
|
return getOAuthSignInResponse(activity, accessToken, Credentials.Type.XAUTH)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 23:01:17 +02:00
|
|
|
@Throws(MicroBlogException::class, AuthenticationException::class)
|
2016-07-07 09:39:32 +02:00
|
|
|
private fun authBasic(): SignInResponse {
|
2016-12-03 06:48:40 +01:00
|
|
|
val activity = activityRef.get() ?: throw InterruptedException()
|
2017-01-07 17:13:11 +01:00
|
|
|
val versionSuffix = if (apiConfig.isNoVersionSuffix) null else "1.1"
|
2016-07-07 09:39:32 +02:00
|
|
|
val endpoint = Endpoint(MicroBlogAPIFactory.getApiUrl(apiUrlFormat, "api",
|
|
|
|
versionSuffix))
|
|
|
|
val auth = BasicAuthorization(username, password)
|
2016-12-08 16:45:07 +01:00
|
|
|
val twitter = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = MicroBlog::class.java)
|
2016-12-03 06:48:40 +01:00
|
|
|
val apiUser: User
|
2016-07-07 09:39:32 +02:00
|
|
|
try {
|
2016-12-03 06:48:40 +01:00
|
|
|
apiUser = twitter.verifyCredentials()
|
2016-07-07 09:39:32 +02:00
|
|
|
} catch (e: MicroBlogException) {
|
|
|
|
if (e.statusCode == 401) {
|
|
|
|
throw WrongBasicCredentialException()
|
|
|
|
} else if (e.statusCode == 404) {
|
|
|
|
throw WrongAPIURLFormatException()
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
|
2016-12-03 06:48:40 +01:00
|
|
|
var color = analyseUserProfileColor(apiUser)
|
2020-06-08 23:01:17 +02:00
|
|
|
val (type, extras) = detectAccountType(twitter, apiUser, apiConfig.type)
|
2017-05-03 15:42:05 +02:00
|
|
|
val accountKey = apiUser.key
|
2017-04-20 19:23:11 +02:00
|
|
|
val user = apiUser.toParcelable(accountKey, type, profileImageSize = profileImageSize)
|
2016-12-15 06:11:32 +01:00
|
|
|
val am = AccountManager.get(activity)
|
|
|
|
val account = AccountUtils.findByAccountKey(am, accountKey)
|
2016-07-07 09:39:32 +02:00
|
|
|
if (account != null) {
|
2016-12-15 06:11:32 +01:00
|
|
|
color = account.getColor(am)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2016-12-03 06:48:40 +01:00
|
|
|
val credentials = BasicCredentials()
|
|
|
|
credentials.api_url_format = apiUrlFormat
|
2017-01-07 17:13:11 +01:00
|
|
|
credentials.no_version_suffix = apiConfig.isNoVersionSuffix
|
2016-12-03 06:48:40 +01:00
|
|
|
credentials.username = username
|
|
|
|
credentials.password = password
|
|
|
|
return SignInResponse(account != null, Credentials.Type.BASIC, credentials, user,
|
2017-04-18 17:10:44 +02:00
|
|
|
color, type, extras)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Throws(MicroBlogException::class)
|
|
|
|
private fun authTwipOMode(): SignInResponse {
|
2016-12-03 06:48:40 +01:00
|
|
|
val activity = activityRef.get() ?: throw InterruptedException()
|
2017-01-07 17:13:11 +01:00
|
|
|
val versionSuffix = if (apiConfig.isNoVersionSuffix) null else "1.1"
|
2016-07-07 09:39:32 +02:00
|
|
|
val endpoint = Endpoint(MicroBlogAPIFactory.getApiUrl(apiUrlFormat, "api",
|
|
|
|
versionSuffix))
|
|
|
|
val auth = EmptyAuthorization()
|
2016-12-08 16:45:07 +01:00
|
|
|
val twitter = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = MicroBlog::class.java)
|
2016-12-03 06:48:40 +01:00
|
|
|
val apiUser = twitter.verifyCredentials()
|
|
|
|
var color = analyseUserProfileColor(apiUser)
|
2020-06-08 23:01:17 +02:00
|
|
|
val (type, extras) = detectAccountType(twitter, apiUser, apiConfig.type)
|
2017-05-03 15:42:05 +02:00
|
|
|
val accountKey = apiUser.key
|
2017-04-20 19:23:11 +02:00
|
|
|
val user = apiUser.toParcelable(accountKey, type, profileImageSize = profileImageSize)
|
2016-12-15 06:11:32 +01:00
|
|
|
val am = AccountManager.get(activity)
|
|
|
|
val account = AccountUtils.findByAccountKey(am, accountKey)
|
2016-07-07 09:39:32 +02:00
|
|
|
if (account != null) {
|
2016-12-15 06:11:32 +01:00
|
|
|
color = account.getColor(am)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2016-12-03 06:48:40 +01:00
|
|
|
val credentials = EmptyCredentials()
|
|
|
|
credentials.api_url_format = apiUrlFormat
|
2017-01-07 17:13:11 +01:00
|
|
|
credentials.no_version_suffix = apiConfig.isNoVersionSuffix
|
2016-12-03 06:48:40 +01:00
|
|
|
|
|
|
|
return SignInResponse(account != null, Credentials.Type.EMPTY, credentials, user, color,
|
2017-04-18 17:10:44 +02:00
|
|
|
type, extras)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(MicroBlogException::class)
|
2017-04-18 11:56:59 +02:00
|
|
|
private fun getOAuthSignInResponse(activity: SignInActivity, accessToken: OAuthToken,
|
2017-09-03 15:23:45 +02:00
|
|
|
@Credentials.Type authType: String): SignInResponse {
|
2017-04-18 11:56:59 +02:00
|
|
|
val auth = apiConfig.getOAuthAuthorization(accessToken) ?:
|
|
|
|
throw MicroBlogException("Invalid OAuth credential")
|
2016-07-07 09:39:32 +02:00
|
|
|
val endpoint = MicroBlogAPIFactory.getOAuthRestEndpoint(apiUrlFormat,
|
2017-01-07 17:13:11 +01:00
|
|
|
apiConfig.isSameOAuthUrl, apiConfig.isNoVersionSuffix)
|
2016-12-08 16:45:07 +01:00
|
|
|
val twitter = newMicroBlogInstance(activity, endpoint = endpoint, auth = auth,
|
2017-02-11 10:03:35 +01:00
|
|
|
accountType = apiConfig.type, cls = MicroBlog::class.java)
|
2016-12-03 06:48:40 +01:00
|
|
|
val apiUser = twitter.verifyCredentials()
|
|
|
|
var color = analyseUserProfileColor(apiUser)
|
2020-06-08 23:01:17 +02:00
|
|
|
val (type, extras) = detectAccountType(twitter, apiUser, apiConfig.type)
|
2017-05-03 15:42:05 +02:00
|
|
|
val accountKey = apiUser.key
|
2017-04-20 19:23:11 +02:00
|
|
|
val user = apiUser.toParcelable(accountKey, type, profileImageSize = profileImageSize)
|
2016-12-15 06:11:32 +01:00
|
|
|
val am = AccountManager.get(activity)
|
|
|
|
val account = AccountUtils.findByAccountKey(am, accountKey)
|
2016-07-07 09:39:32 +02:00
|
|
|
if (account != null) {
|
2016-12-15 06:11:32 +01:00
|
|
|
color = account.getColor(am)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2016-12-03 06:48:40 +01:00
|
|
|
val credentials = OAuthCredentials()
|
|
|
|
credentials.api_url_format = apiUrlFormat
|
2017-01-07 17:13:11 +01:00
|
|
|
credentials.no_version_suffix = apiConfig.isNoVersionSuffix
|
2016-12-03 06:48:40 +01:00
|
|
|
|
2017-01-07 17:13:11 +01:00
|
|
|
credentials.same_oauth_signing_url = apiConfig.isSameOAuthUrl
|
2016-12-03 06:48:40 +01:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
credentials.consumer_key = auth.consumerKey
|
|
|
|
credentials.consumer_secret = auth.consumerSecret
|
2016-12-03 06:48:40 +01:00
|
|
|
credentials.access_token = accessToken.oauthToken
|
|
|
|
credentials.access_token_secret = accessToken.oauthTokenSecret
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
return SignInResponse(account != null, authType, credentials, user, color, type, extras)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 23:01:17 +02:00
|
|
|
internal class WrongBasicCredentialException : AuthenticationException()
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2020-06-08 23:01:17 +02:00
|
|
|
internal class WrongAPIURLFormatException : AuthenticationException()
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2020-06-08 23:01:17 +02:00
|
|
|
internal inner class InputLoginVerificationCallback : LoginVerificationCallback {
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2016-07-14 14:00:27 +02:00
|
|
|
override fun getLoginVerification(challengeType: String): String? {
|
2016-07-07 09:39:32 +02:00
|
|
|
// Dismiss current progress dialog
|
|
|
|
publishProgress(Runnable {
|
2017-08-28 11:50:04 +02:00
|
|
|
activityRef.get()?.dismissSignInProgressDialog()
|
2016-07-07 09:39:32 +02:00
|
|
|
})
|
2017-08-28 11:50:04 +02:00
|
|
|
val deferred = deferred<String?, Exception>()
|
2016-07-07 09:39:32 +02:00
|
|
|
// Show verification input dialog and wait for user input
|
|
|
|
publishProgress(Runnable {
|
|
|
|
val activity = activityRef.get() ?: return@Runnable
|
2017-08-28 11:50:04 +02:00
|
|
|
activity.executeAfterFragmentResumed {
|
|
|
|
val sia = it as SignInActivity
|
2016-07-07 09:39:32 +02:00
|
|
|
val df = InputLoginVerificationDialogFragment()
|
|
|
|
df.isCancelable = false
|
2017-08-28 11:50:04 +02:00
|
|
|
df.deferred = deferred
|
2016-07-12 03:22:33 +02:00
|
|
|
df.challengeType = challengeType
|
2016-12-15 06:49:18 +01:00
|
|
|
df.show(sia.supportFragmentManager, "login_challenge_$challengeType")
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2017-08-28 11:50:04 +02:00
|
|
|
return try {
|
|
|
|
deferred.promise.get()
|
|
|
|
} catch (e: CancelException) {
|
|
|
|
throw MicroBlogException(e)
|
|
|
|
} finally {
|
|
|
|
// Show progress dialog
|
|
|
|
publishProgress(Runnable {
|
|
|
|
activityRef.get()?.showSignInProgressDialog()
|
|
|
|
})
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-04-18 17:10:44 +02:00
|
|
|
internal abstract class AbstractSignInTask(activity: SignInActivity) : AsyncTask<Any, Runnable, SingleResponse<SignInResponse>>() {
|
|
|
|
protected val activityRef = WeakReference(activity)
|
|
|
|
|
|
|
|
protected val profileImageSize: String = activity.getString(R.string.profile_image_size)
|
|
|
|
|
2019-10-24 17:52:11 +02:00
|
|
|
final override fun doInBackground(vararg args: Any?): SingleResponse<SignInResponse> {
|
2020-06-08 23:19:10 +02:00
|
|
|
return try {
|
|
|
|
SingleResponse.getInstance(performLogin())
|
2017-04-18 17:10:44 +02:00
|
|
|
} catch (e: Exception) {
|
2020-06-08 23:19:10 +02:00
|
|
|
SingleResponse.getInstance(e)
|
2017-04-18 17:10:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract fun performLogin(): SignInResponse
|
|
|
|
|
|
|
|
override fun onPostExecute(result: SingleResponse<SignInResponse>) {
|
|
|
|
val activity = activityRef.get()
|
|
|
|
activity?.dismissDialogFragment(FRAGMENT_TAG_SIGN_IN_PROGRESS)
|
|
|
|
if (result.hasData()) {
|
|
|
|
activity?.onSignInResult(result.data!!)
|
|
|
|
} else {
|
|
|
|
activity?.onSignInError(result.exception!!)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPreExecute() {
|
|
|
|
val activity = activityRef.get()
|
|
|
|
activity?.onSignInStart()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onProgressUpdate(vararg values: Runnable) {
|
|
|
|
for (value in values) {
|
|
|
|
value.run()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(MicroBlogException::class)
|
|
|
|
internal fun analyseUserProfileColor(user: User?): Int {
|
|
|
|
if (user == null) throw MicroBlogException("Unable to get user info")
|
|
|
|
return ParcelableUserUtils.parseColor(user.profileLinkColor)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-04-18 15:19:07 +02:00
|
|
|
internal data class SignInResponse(
|
|
|
|
val alreadyLoggedIn: Boolean,
|
|
|
|
@Credentials.Type val credsType: String = Credentials.Type.EMPTY,
|
|
|
|
val credentials: Credentials,
|
|
|
|
val user: ParcelableUser,
|
|
|
|
val color: Int = 0,
|
2017-04-18 17:10:44 +02:00
|
|
|
val type: String,
|
|
|
|
val extras: AccountExtras?
|
2017-04-18 15:19:07 +02:00
|
|
|
) {
|
|
|
|
|
|
|
|
private fun writeAccountInfo(action: (k: String, v: String?) -> Unit) {
|
|
|
|
action(ACCOUNT_USER_DATA_KEY, user.key.toString())
|
2017-04-18 17:10:44 +02:00
|
|
|
action(ACCOUNT_USER_DATA_TYPE, type)
|
2017-04-18 15:19:07 +02:00
|
|
|
action(ACCOUNT_USER_DATA_CREDS_TYPE, credsType)
|
|
|
|
|
|
|
|
action(ACCOUNT_USER_DATA_ACTIVATED, true.toString())
|
|
|
|
action(ACCOUNT_USER_DATA_COLOR, toHexColor(color, format = HexColorFormat.RGB))
|
|
|
|
|
|
|
|
action(ACCOUNT_USER_DATA_USER, JsonSerializer.serialize(user))
|
2017-04-18 17:10:44 +02:00
|
|
|
action(ACCOUNT_USER_DATA_EXTRAS, extras?.let { JsonSerializer.serialize(it) })
|
2017-04-18 15:19:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun writeAuthToken(am: AccountManager, account: Account) {
|
|
|
|
val authToken = JsonSerializer.serialize(credentials)
|
|
|
|
am.setAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE, authToken)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun updateAccount(am: AccountManager) {
|
|
|
|
val account = AccountUtils.findByAccountKey(am, user.key) ?: return
|
|
|
|
writeAccountInfo { k, v ->
|
|
|
|
am.setUserData(account, k, v)
|
|
|
|
}
|
|
|
|
writeAuthToken(am, account)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun addAccount(am: AccountManager, randomizeAccountName: Boolean): Account {
|
|
|
|
var accountName: String
|
|
|
|
if (randomizeAccountName) {
|
|
|
|
val usedNames = ArraySet<String>()
|
|
|
|
AccountUtils.getAccounts(am).mapTo(usedNames, Account::name)
|
|
|
|
do {
|
|
|
|
accountName = UUID.randomUUID().toString()
|
|
|
|
} while (accountName in usedNames)
|
|
|
|
} else {
|
|
|
|
accountName = generateAccountName(user.screen_name, user.key.host)
|
|
|
|
}
|
|
|
|
val account = Account(accountName, ACCOUNT_TYPE)
|
|
|
|
val accountPosition = AccountUtils.getAccounts(am).size
|
|
|
|
// Don't add UserData in this method, see http://stackoverflow.com/a/29776224/859190
|
|
|
|
am.addAccountExplicitly(account, null, null)
|
|
|
|
writeAccountInfo { k, v ->
|
|
|
|
am.setUserData(account, k, v)
|
|
|
|
}
|
|
|
|
am.setUserData(account, ACCOUNT_USER_DATA_POSITION, accountPosition.toString())
|
|
|
|
writeAuthToken(am, account)
|
|
|
|
return account
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
|
|
|
companion object {
|
2017-04-18 15:19:07 +02:00
|
|
|
const val REQUEST_BROWSER_TWITTER_SIGN_IN = 101
|
|
|
|
const val REQUEST_BROWSER_MASTODON_SIGN_IN = 102
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2020-06-08 23:11:06 +02:00
|
|
|
private const val FRAGMENT_TAG_SIGN_IN_PROGRESS = "sign_in_progress"
|
|
|
|
private const val EXTRA_API_LAST_CHANGE = "api_last_change"
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-01-07 17:13:11 +01:00
|
|
|
@Throws(IOException::class)
|
|
|
|
internal fun detectAccountType(twitter: MicroBlog, user: User, type: String?): Pair<String, AccountExtras?> {
|
|
|
|
when (type) {
|
|
|
|
AccountType.STATUSNET -> {
|
2017-04-19 12:59:39 +02:00
|
|
|
return Pair(type, getStatusNetAccountExtras(twitter))
|
2017-01-07 17:13:11 +01:00
|
|
|
}
|
|
|
|
AccountType.TWITTER -> {
|
2017-04-19 12:59:39 +02:00
|
|
|
return Pair(type, getTwitterAccountExtras(twitter))
|
2017-01-07 17:13:11 +01:00
|
|
|
}
|
|
|
|
AccountType.FANFOU -> {
|
|
|
|
return Pair(AccountType.FANFOU, null)
|
|
|
|
}
|
|
|
|
else -> {
|
2017-05-03 15:42:05 +02:00
|
|
|
if (user.isFanfouUser) {
|
2017-01-07 17:13:11 +01:00
|
|
|
return Pair(AccountType.FANFOU, null)
|
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
|
|
|
}
|
2016-12-12 06:42:27 +01:00
|
|
|
return Pair(AccountType.TWITTER, null)
|
2016-07-07 09:39:32 +02:00
|
|
|
}
|
2017-01-25 13:08:23 +01:00
|
|
|
|
2017-04-19 12:59:39 +02:00
|
|
|
private fun getStatusNetAccountExtras(twitter: MicroBlog): StatusNetAccountExtras {
|
2017-01-25 13:08:23 +01:00
|
|
|
// Get StatusNet specific resource
|
|
|
|
val config = twitter.statusNetConfig
|
2017-05-24 07:34:33 +02:00
|
|
|
return StatusNetAccountExtras().apply {
|
|
|
|
textLimit = config.site?.textLimit ?: -1
|
|
|
|
uploadLimit = config.attachments?.fileQuota ?: -1L
|
2017-01-25 13:08:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:59:39 +02:00
|
|
|
private fun getTwitterAccountExtras(twitter: MicroBlog): TwitterAccountExtras {
|
2020-04-03 11:14:42 +02:00
|
|
|
val extras = TwitterAccountExtras()
|
|
|
|
try {
|
|
|
|
// Get Twitter official only resource
|
|
|
|
val paging = Paging()
|
|
|
|
paging.count(1)
|
|
|
|
twitter.getActivitiesAboutMe(paging)
|
|
|
|
extras.setIsOfficialCredentials(true)
|
|
|
|
} catch (e: MicroBlogException) {
|
|
|
|
// Ignore
|
|
|
|
}
|
|
|
|
return extras
|
2017-04-19 12:59:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun getMastodonAccountExtras(mastodon: Mastodon): MastodonAccountExtras {
|
|
|
|
return MastodonAccountExtras()
|
2017-01-25 13:08:23 +01:00
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
2017-04-18 11:56:59 +02:00
|
|
|
private val CustomAPIConfig.signUpUrlOrDefault: String?
|
|
|
|
get() = signUpUrl ?: when (type) {
|
|
|
|
AccountType.TWITTER -> "https://twitter.com/signup"
|
2017-06-25 05:36:54 +02:00
|
|
|
AccountType.FANFOU -> "https://fanfou.com/register"
|
2017-04-18 11:56:59 +02:00
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
|
2017-08-28 11:50:04 +02:00
|
|
|
}
|
2016-07-07 09:39:32 +02:00
|
|
|
|
|
|
|
}
|