MainActivity.kt 14 KB

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