123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- /*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017 Mario Danic (mario@lovelyhq.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 com.nextcloud.talk.controllers
- import android.annotation.SuppressLint
- import android.content.pm.ActivityInfo
- import android.os.Bundle
- import android.os.Handler
- import android.text.TextUtils
- import android.view.View
- import androidx.work.Data
- import androidx.work.OneTimeWorkRequest
- import androidx.work.WorkManager
- import autodagger.AutoInjector
- import com.bluelinelabs.conductor.RouterTransaction
- import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
- import com.nextcloud.talk.R
- import com.nextcloud.talk.api.NcApi
- import com.nextcloud.talk.application.NextcloudTalkApplication
- import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
- import com.nextcloud.talk.controllers.base.NewBaseController
- import com.nextcloud.talk.controllers.util.viewBinding
- import com.nextcloud.talk.databinding.ControllerAccountVerificationBinding
- import com.nextcloud.talk.events.EventStatus
- import com.nextcloud.talk.jobs.CapabilitiesWorker
- import com.nextcloud.talk.jobs.PushRegistrationWorker
- import com.nextcloud.talk.jobs.SignalingSettingsWorker
- import com.nextcloud.talk.models.database.UserEntity
- import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
- import com.nextcloud.talk.models.json.generic.Status
- import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
- import com.nextcloud.talk.utils.ApiUtils
- import com.nextcloud.talk.utils.ClosedInterfaceImpl
- import com.nextcloud.talk.utils.UriUtils
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
- import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
- import com.nextcloud.talk.utils.database.user.UserUtils
- import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
- import io.reactivex.CompletableObserver
- import io.reactivex.Observer
- import io.reactivex.android.schedulers.AndroidSchedulers
- import io.reactivex.disposables.Disposable
- import io.reactivex.schedulers.Schedulers
- import org.greenrobot.eventbus.EventBus
- import org.greenrobot.eventbus.Subscribe
- import org.greenrobot.eventbus.ThreadMode
- import java.net.CookieManager
- import java.util.ArrayList
- import javax.inject.Inject
- @AutoInjector(NextcloudTalkApplication::class)
- class AccountVerificationController(args: Bundle? = null) :
- NewBaseController(
- R.layout.controller_account_verification,
- args
- ) {
- private val binding: ControllerAccountVerificationBinding by viewBinding(ControllerAccountVerificationBinding::bind)
- @Inject
- lateinit var ncApi: NcApi
- @Inject
- lateinit var userUtils: UserUtils
- @Inject
- lateinit var cookieManager: CookieManager
- @Inject
- lateinit var eventBus: EventBus
- private var internalAccountId: Long = -1
- private val disposables: MutableList<Disposable> = ArrayList()
- private var baseUrl: String? = null
- private var username: String? = null
- private var token: String? = null
- private var isAccountImport = false
- private var originalProtocol: String? = null
- override fun onAttach(view: View) {
- super.onAttach(view)
- eventBus.register(this)
- }
- override fun onDetach(view: View) {
- super.onDetach(view)
- eventBus.unregister(this)
- }
- override fun onViewBound(view: View) {
- super.onViewBound(view)
- activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- actionBar?.hide()
- if (
- isAccountImport &&
- !UriUtils.hasHttpProtocollPrefixed(baseUrl!!) ||
- isSameProtocol(baseUrl!!, originalProtocol!!)
- ) {
- determineBaseUrlProtocol(true)
- } else {
- checkEverything()
- }
- }
- private fun isSameProtocol(baseUrl: String, originalProtocol: String): Boolean {
- return !TextUtils.isEmpty(originalProtocol) && !baseUrl.startsWith(originalProtocol)
- }
- private fun checkEverything() {
- val credentials = ApiUtils.getCredentials(username, token)
- cookieManager.cookieStore.removeAll()
- findServerTalkApp(credentials)
- }
- private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) {
- cookieManager.cookieStore.removeAll()
- baseUrl = baseUrl!!.replace("http://", "").replace("https://", "")
- val queryUrl: String = if (checkForcedHttps) {
- "https://" + baseUrl + ApiUtils.getUrlPostfixForStatus()
- } else {
- "http://" + baseUrl + ApiUtils.getUrlPostfixForStatus()
- }
- ncApi.getServerStatus(queryUrl)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(object : Observer<Status?> {
- override fun onSubscribe(d: Disposable) {
- disposables.add(d)
- }
- override fun onNext(status: Status) {
- baseUrl = if (checkForcedHttps) {
- "https://$baseUrl"
- } else {
- "http://$baseUrl"
- }
- if (isAccountImport) {
- router.replaceTopController(
- RouterTransaction.with(
- WebViewLoginController(
- baseUrl,
- false,
- username,
- ""
- )
- )
- .pushChangeHandler(HorizontalChangeHandler())
- .popChangeHandler(HorizontalChangeHandler())
- )
- } else {
- checkEverything()
- }
- }
- override fun onError(e: Throwable) {
- if (checkForcedHttps) {
- determineBaseUrlProtocol(false)
- } else {
- abortVerification()
- }
- }
- override fun onComplete() {
- // unused atm
- }
- })
- }
- private fun findServerTalkApp(credentials: String) {
- ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
- .subscribeOn(Schedulers.io())
- .subscribe(object : Observer<CapabilitiesOverall> {
- override fun onSubscribe(d: Disposable) {
- disposables.add(d)
- }
- override fun onNext(capabilitiesOverall: CapabilitiesOverall) {
- val hasTalk =
- capabilitiesOverall.ocs!!.data!!.capabilities != null &&
- capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability != null &&
- capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null &&
- !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty()
- if (hasTalk) {
- fetchProfile(credentials)
- } else {
- if (activity != null && resources != null) {
- activity!!.runOnUiThread {
- binding.progressText.setText(
- String.format(
- resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
- resources!!.getString(R.string.nc_app_product_name)
- )
- )
- }
- }
- ApplicationWideMessageHolder.getInstance().setMessageType(
- ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
- )
- abortVerification()
- }
- }
- override fun onError(e: Throwable) {
- if (activity != null && resources != null) {
- activity!!.runOnUiThread {
- binding.progressText.setText(
- String.format(
- resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
- resources!!.getString(R.string.nc_app_product_name)
- )
- )
- }
- }
- ApplicationWideMessageHolder.getInstance().setMessageType(
- ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
- )
- abortVerification()
- }
- override fun onComplete() {
- // unused atm
- }
- })
- }
- private fun storeProfile(displayName: String?, userId: String) {
- userUtils.createOrUpdateUser(
- username, token,
- baseUrl, displayName, null, java.lang.Boolean.TRUE,
- userId, null, null,
- appPreferences!!.temporaryClientCertAlias, null
- )
- .subscribeOn(Schedulers.io())
- .subscribe(object : Observer<UserEntity> {
- override fun onSubscribe(d: Disposable) {
- disposables.add(d)
- }
- @SuppressLint("SetTextI18n")
- override fun onNext(userEntity: UserEntity) {
- internalAccountId = userEntity.id
- if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
- registerForPush()
- } else {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_push_disabled)}
- """.trimIndent()
- }
- fetchAndStoreCapabilities()
- }
- }
- @SuppressLint("SetTextI18n")
- override fun onError(e: Throwable) {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- """.trimIndent() +
- resources!!.getString(R.string.nc_display_name_not_stored)
- abortVerification()
- }
- override fun onComplete() {
- // unused atm
- }
- })
- }
- private fun fetchProfile(credentials: String) {
- ncApi.getUserProfile(
- credentials,
- ApiUtils.getUrlForUserProfile(baseUrl)
- )
- .subscribeOn(Schedulers.io())
- .subscribe(object : Observer<UserProfileOverall> {
- override fun onSubscribe(d: Disposable) {
- disposables.add(d)
- }
- @SuppressLint("SetTextI18n")
- override fun onNext(userProfileOverall: UserProfileOverall) {
- var displayName: String? = null
- if (!TextUtils.isEmpty(userProfileOverall.ocs.data.displayName)) {
- displayName = userProfileOverall.ocs.data.displayName
- } else if (!TextUtils.isEmpty(userProfileOverall.ocs.data.displayNameAlt)) {
- displayName = userProfileOverall.ocs.data.displayNameAlt
- }
- if (!TextUtils.isEmpty(displayName)) {
- storeProfile(displayName, userProfileOverall.ocs.data.userId)
- } else {
- if (activity != null) {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_display_name_not_fetched)}
- """.trimIndent()
- }
- }
- abortVerification()
- }
- }
- @SuppressLint("SetTextI18n")
- override fun onError(e: Throwable) {
- if (activity != null) {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_display_name_not_fetched)}
- """.trimIndent()
- }
- }
- abortVerification()
- }
- override fun onComplete() {
- // unused atm
- }
- })
- }
- private fun registerForPush() {
- val data =
- Data.Builder()
- .putString(PushRegistrationWorker.ORIGIN, "AccountVerificationController#registerForPush")
- .build()
- val pushRegistrationWork =
- OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
- .setInputData(data)
- .build()
- WorkManager.getInstance().enqueue(pushRegistrationWork)
- }
- @SuppressLint("SetTextI18n")
- @Subscribe(threadMode = ThreadMode.BACKGROUND)
- fun onMessageEvent(eventStatus: EventStatus) {
- if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) {
- if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood && activity != null) {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_push_disabled)}
- """.trimIndent()
- }
- }
- fetchAndStoreCapabilities()
- } else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) {
- if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
- if (activity != null) {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_capabilities_failed)}
- """.trimIndent()
- }
- }
- abortVerification()
- } else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) {
- fetchAndStoreExternalSignalingSettings()
- }
- } else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) {
- if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
- if (activity != null) {
- activity!!.runOnUiThread {
- binding.progressText.text =
- """
- ${binding.progressText.text}
- ${resources!!.getString(R.string.nc_external_server_failed)}
- """.trimIndent()
- }
- }
- }
- proceedWithLogin()
- }
- }
- private fun fetchAndStoreCapabilities() {
- val userData =
- Data.Builder()
- .putLong(KEY_INTERNAL_USER_ID, internalAccountId)
- .build()
- val pushNotificationWork =
- OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java)
- .setInputData(userData)
- .build()
- WorkManager.getInstance().enqueue(pushNotificationWork)
- }
- private fun fetchAndStoreExternalSignalingSettings() {
- val userData =
- Data.Builder()
- .putLong(KEY_INTERNAL_USER_ID, internalAccountId)
- .build()
- val signalingSettings =
- OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
- .setInputData(userData)
- .build()
- WorkManager.getInstance().enqueue(signalingSettings)
- }
- private fun proceedWithLogin() {
- cookieManager.cookieStore.removeAll()
- userUtils.disableAllUsersWithoutId(internalAccountId)
- if (activity != null) {
- activity!!.runOnUiThread {
- if (userUtils.users.size == 1) {
- router.setRoot(
- RouterTransaction.with(ConversationsListController(Bundle()))
- .pushChangeHandler(HorizontalChangeHandler())
- .popChangeHandler(HorizontalChangeHandler())
- )
- } else {
- if (isAccountImport) {
- ApplicationWideMessageHolder.getInstance().messageType =
- ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED
- }
- router.popToRoot()
- }
- }
- }
- }
- private fun dispose() {
- for (i in disposables.indices) {
- if (!disposables[i].isDisposed) {
- disposables[i].dispose()
- }
- }
- }
- override fun onDestroyView(view: View) {
- super.onDestroyView(view)
- activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
- }
- public override fun onDestroy() {
- dispose()
- super.onDestroy()
- }
- private fun abortVerification() {
- if (!isAccountImport) {
- if (internalAccountId != -1L) {
- userUtils.deleteUserWithId(internalAccountId).subscribe(object : CompletableObserver {
- override fun onSubscribe(d: Disposable) {
- // unused atm
- }
- override fun onComplete() {
- activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
- }
- override fun onError(e: Throwable) {
- // unused atm
- }
- })
- } else {
- activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
- }
- } else {
- ApplicationWideMessageHolder.getInstance().setMessageType(
- ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
- )
- activity?.runOnUiThread {
- Handler().postDelayed({
- if (router.hasRootController()) {
- if (activity != null) {
- router.popToRoot()
- }
- } else {
- if (userUtils.anyUserExists()) {
- router.setRoot(
- RouterTransaction.with(ConversationsListController(Bundle()))
- .pushChangeHandler(HorizontalChangeHandler())
- .popChangeHandler(HorizontalChangeHandler())
- )
- } else {
- router.setRoot(
- RouterTransaction.with(ServerSelectionController())
- .pushChangeHandler(HorizontalChangeHandler())
- .popChangeHandler(HorizontalChangeHandler())
- )
- }
- }
- }, DELAY_IN_MILLIS)
- }
- }
- }
- companion object {
- const val TAG = "AccountVerificationController"
- const val DELAY_IN_MILLIS: Long = 7500
- }
- init {
- sharedApplication!!.componentApplication.inject(this)
- if (args != null) {
- baseUrl = args.getString(KEY_BASE_URL)
- username = args.getString(KEY_USERNAME)
- token = args.getString(KEY_TOKEN)
- if (args.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
- isAccountImport = true
- }
- if (args.containsKey(KEY_ORIGINAL_PROTOCOL)) {
- originalProtocol = args.getString(KEY_ORIGINAL_PROTOCOL)
- }
- }
- }
- }
|