MainActivity.kt 16 KB

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