MainActivity.kt 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * @author Andy Scherzinger
  6. * @author Marcel Hibbe
  7. * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
  8. * Copyright (C) 2021 Andy Scherzinger (infoi@andy-scherzinger.de)
  9. * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License as published by
  13. * the Free Software Foundation, either version 3 of the License, or
  14. * at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. */
  24. package com.nextcloud.talk.activities
  25. import android.app.KeyguardManager
  26. import android.content.Context
  27. import android.content.Intent
  28. import android.os.Bundle
  29. import android.provider.ContactsContract
  30. import android.text.TextUtils
  31. import android.util.Log
  32. import androidx.activity.OnBackPressedCallback
  33. import androidx.lifecycle.DefaultLifecycleObserver
  34. import androidx.lifecycle.LifecycleOwner
  35. import androidx.lifecycle.ProcessLifecycleOwner
  36. import autodagger.AutoInjector
  37. import com.bluelinelabs.conductor.Conductor
  38. import com.bluelinelabs.conductor.Router
  39. import com.bluelinelabs.conductor.RouterTransaction
  40. import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
  41. import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
  42. import com.google.android.material.snackbar.Snackbar
  43. import com.nextcloud.talk.BuildConfig
  44. import com.nextcloud.talk.R
  45. import com.nextcloud.talk.api.NcApi
  46. import com.nextcloud.talk.application.NextcloudTalkApplication
  47. import com.nextcloud.talk.chat.ChatActivity
  48. import com.nextcloud.talk.controllers.ServerSelectionController
  49. import com.nextcloud.talk.controllers.WebViewLoginController
  50. import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
  51. import com.nextcloud.talk.conversationlist.ConversationsListActivity
  52. import com.nextcloud.talk.data.user.model.User
  53. import com.nextcloud.talk.databinding.ActivityMainBinding
  54. import com.nextcloud.talk.lock.LockedActivity
  55. import com.nextcloud.talk.models.json.conversations.RoomOverall
  56. import com.nextcloud.talk.users.UserManager
  57. import com.nextcloud.talk.utils.ApiUtils
  58. import com.nextcloud.talk.utils.SecurityUtils
  59. import com.nextcloud.talk.utils.bundle.BundleKeys
  60. import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT
  61. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
  62. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
  63. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
  64. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
  65. import io.reactivex.Observer
  66. import io.reactivex.SingleObserver
  67. import io.reactivex.android.schedulers.AndroidSchedulers
  68. import io.reactivex.disposables.Disposable
  69. import io.reactivex.schedulers.Schedulers
  70. import org.parceler.Parcels
  71. import javax.inject.Inject
  72. @AutoInjector(NextcloudTalkApplication::class)
  73. class MainActivity : BaseActivity(), ActionBarProvider {
  74. lateinit var binding: ActivityMainBinding
  75. private val callback = object : OnBackPressedCallback(true) {
  76. override fun handleOnBackPressed() {
  77. finish()
  78. }
  79. }
  80. @Inject
  81. lateinit var ncApi: NcApi
  82. @Inject
  83. lateinit var userManager: UserManager
  84. private var router: Router? = null
  85. @Suppress("Detekt.TooGenericExceptionCaught")
  86. override fun onCreate(savedInstanceState: Bundle?) {
  87. Log.d(TAG, "onCreate: Activity: " + System.identityHashCode(this).toString())
  88. super.onCreate(savedInstanceState)
  89. ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
  90. override fun onStart(owner: LifecycleOwner) {
  91. lockScreenIfConditionsApply()
  92. }
  93. })
  94. // Set the default theme to replace the launch screen theme.
  95. setTheme(R.style.AppTheme)
  96. binding = ActivityMainBinding.inflate(layoutInflater)
  97. setContentView(binding.root)
  98. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  99. setSupportActionBar(binding.toolbar)
  100. router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
  101. if (intent.hasExtra(ADD_ACCOUNT) && intent.getBooleanExtra(ADD_ACCOUNT, false)) {
  102. addAccount()
  103. } else if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
  104. onNewIntent(intent)
  105. } else if (!router!!.hasRootController()) {
  106. if (!appPreferences.isDbRoomMigrated) {
  107. appPreferences.isDbRoomMigrated = true
  108. }
  109. userManager.users.subscribe(object : SingleObserver<List<User>> {
  110. override fun onSubscribe(d: Disposable) {
  111. // unused atm
  112. }
  113. override fun onSuccess(users: List<User>) {
  114. if (users.isNotEmpty()) {
  115. runOnUiThread {
  116. openConversationList()
  117. }
  118. } else {
  119. runOnUiThread {
  120. launchLoginScreen()
  121. }
  122. }
  123. }
  124. override fun onError(e: Throwable) {
  125. Log.e(TAG, "Error loading existing users", e)
  126. }
  127. })
  128. }
  129. }
  130. fun lockScreenIfConditionsApply() {
  131. val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
  132. if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
  133. if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
  134. val lockIntent = Intent(context, LockedActivity::class.java)
  135. startActivity(lockIntent)
  136. }
  137. }
  138. }
  139. private fun launchLoginScreen() {
  140. if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
  141. router!!.pushController(
  142. RouterTransaction.with(
  143. WebViewLoginController(resources.getString(R.string.weblogin_url), false)
  144. )
  145. .pushChangeHandler(HorizontalChangeHandler())
  146. .popChangeHandler(HorizontalChangeHandler())
  147. )
  148. } else {
  149. router!!.setRoot(
  150. RouterTransaction.with(ServerSelectionController())
  151. .pushChangeHandler(HorizontalChangeHandler())
  152. .popChangeHandler(HorizontalChangeHandler())
  153. )
  154. }
  155. }
  156. override fun onStart() {
  157. Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
  158. super.onStart()
  159. logRouterBackStack(router!!)
  160. }
  161. override fun onResume() {
  162. Log.d(TAG, "onResume: Activity: " + System.identityHashCode(this).toString())
  163. super.onResume()
  164. if (appPreferences.isScreenLocked) {
  165. SecurityUtils.createKey(appPreferences.screenLockTimeout)
  166. }
  167. }
  168. override fun onPause() {
  169. Log.d(TAG, "onPause: Activity: " + System.identityHashCode(this).toString())
  170. super.onPause()
  171. }
  172. override fun onStop() {
  173. Log.d(TAG, "onStop: Activity: " + System.identityHashCode(this).toString())
  174. super.onStop()
  175. }
  176. private fun openConversationList() {
  177. val intent = Intent(this, ConversationsListActivity::class.java)
  178. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  179. intent.putExtras(Bundle())
  180. startActivity(intent)
  181. }
  182. fun addAccount() {
  183. router!!.pushController(
  184. RouterTransaction.with(ServerSelectionController())
  185. .pushChangeHandler(VerticalChangeHandler())
  186. .popChangeHandler(VerticalChangeHandler())
  187. )
  188. }
  189. private fun handleActionFromContact(intent: Intent) {
  190. if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
  191. val cursor = contentResolver.query(intent.data!!, null, null, null, null)
  192. var userId = ""
  193. if (cursor != null) {
  194. if (cursor.moveToFirst()) {
  195. // userId @ server
  196. userId = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.DATA1))
  197. }
  198. cursor.close()
  199. }
  200. when (intent.type) {
  201. "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" -> {
  202. val user = userId.substringBeforeLast("@")
  203. val baseUrl = userId.substringAfterLast("@")
  204. if (userManager.currentUser.blockingGet()?.baseUrl?.endsWith(baseUrl) == true) {
  205. startConversation(user)
  206. } else {
  207. Snackbar.make(
  208. binding.controllerContainer,
  209. R.string.nc_phone_book_integration_account_not_found,
  210. Snackbar.LENGTH_LONG
  211. ).show()
  212. }
  213. }
  214. }
  215. }
  216. }
  217. private fun startConversation(userId: String) {
  218. val roomType = "1"
  219. val currentUser = userManager.currentUser.blockingGet()
  220. val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
  221. val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
  222. val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
  223. apiVersion,
  224. currentUser?.baseUrl,
  225. roomType,
  226. null,
  227. userId,
  228. null
  229. )
  230. ncApi.createRoom(
  231. credentials,
  232. retrofitBucket.url,
  233. retrofitBucket.queryMap
  234. )
  235. .subscribeOn(Schedulers.io())
  236. .observeOn(AndroidSchedulers.mainThread())
  237. .subscribe(object : Observer<RoomOverall> {
  238. override fun onSubscribe(d: Disposable) {
  239. // unused atm
  240. }
  241. override fun onNext(roomOverall: RoomOverall) {
  242. val bundle = Bundle()
  243. bundle.putParcelable(KEY_USER_ENTITY, currentUser)
  244. bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
  245. bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
  246. // FIXME once APIv2 or later is used only, the createRoom already returns all the data
  247. ncApi.getRoom(
  248. credentials,
  249. ApiUtils.getUrlForRoom(
  250. apiVersion,
  251. currentUser?.baseUrl,
  252. roomOverall.ocs!!.data!!.token
  253. )
  254. )
  255. .subscribeOn(Schedulers.io())
  256. .observeOn(AndroidSchedulers.mainThread())
  257. .subscribe(object : Observer<RoomOverall> {
  258. override fun onSubscribe(d: Disposable) {
  259. // unused atm
  260. }
  261. override fun onNext(roomOverall: RoomOverall) {
  262. bundle.putParcelable(
  263. KEY_ACTIVE_CONVERSATION,
  264. Parcels.wrap(roomOverall.ocs!!.data)
  265. )
  266. val chatIntent = Intent(context, ChatActivity::class.java)
  267. chatIntent.putExtras(bundle)
  268. startActivity(chatIntent)
  269. }
  270. override fun onError(e: Throwable) {
  271. // unused atm
  272. }
  273. override fun onComplete() {
  274. // unused atm
  275. }
  276. })
  277. }
  278. override fun onError(e: Throwable) {
  279. // unused atm
  280. }
  281. override fun onComplete() {
  282. // unused atm
  283. }
  284. })
  285. }
  286. override fun onNewIntent(intent: Intent) {
  287. super.onNewIntent(intent)
  288. Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString())
  289. val user = intent.getParcelableExtra<User>(KEY_USER_ENTITY)
  290. if (user != null && userManager.setUserAsActive(user).blockingGet()) {
  291. handleIntent(intent)
  292. }
  293. }
  294. private fun handleIntent(intent: Intent) {
  295. handleActionFromContact(intent)
  296. if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
  297. if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
  298. if (!router!!.hasRootController()) {
  299. openConversationList()
  300. }
  301. val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
  302. intent.extras?.let { callNotificationIntent.putExtras(it) }
  303. startActivity(callNotificationIntent)
  304. } else {
  305. logRouterBackStack(router!!)
  306. val chatIntent = Intent(context, ChatActivity::class.java)
  307. chatIntent.putExtras(intent.extras!!)
  308. startActivity(chatIntent)
  309. logRouterBackStack(router!!)
  310. }
  311. }
  312. }
  313. override fun onBackPressed() {
  314. if (!router!!.handleBack()) {
  315. // super.onBackPressed()
  316. onBackPressedDispatcher.addCallback(this, callback)
  317. }
  318. }
  319. private fun logRouterBackStack(router: Router) {
  320. if (BuildConfig.DEBUG) {
  321. val backstack = router.backstack
  322. var routerTransaction: RouterTransaction?
  323. Log.d(TAG, " backstack size: " + router.backstackSize)
  324. for (i in 0 until router.backstackSize) {
  325. routerTransaction = backstack[i]
  326. Log.d(TAG, " controller: " + routerTransaction.controller)
  327. }
  328. }
  329. }
  330. companion object {
  331. private const val TAG = "MainActivity"
  332. }
  333. }