MainActivity.kt 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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.Build
  27. import android.os.Bundle
  28. import android.provider.ContactsContract
  29. import android.text.TextUtils
  30. import android.util.Log
  31. import androidx.annotation.RequiresApi
  32. import autodagger.AutoInjector
  33. import com.bluelinelabs.conductor.Conductor
  34. import com.bluelinelabs.conductor.Router
  35. import com.bluelinelabs.conductor.RouterTransaction
  36. import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
  37. import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
  38. import com.google.android.material.snackbar.Snackbar
  39. import com.nextcloud.talk.BuildConfig
  40. import com.nextcloud.talk.R
  41. import com.nextcloud.talk.api.NcApi
  42. import com.nextcloud.talk.application.NextcloudTalkApplication
  43. import com.nextcloud.talk.controllers.ConversationsListController
  44. import com.nextcloud.talk.controllers.LockedController
  45. import com.nextcloud.talk.controllers.ServerSelectionController
  46. import com.nextcloud.talk.controllers.SettingsController
  47. import com.nextcloud.talk.controllers.WebViewLoginController
  48. import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
  49. import com.nextcloud.talk.data.user.model.User
  50. import com.nextcloud.talk.databinding.ActivityMainBinding
  51. import com.nextcloud.talk.models.json.conversations.RoomOverall
  52. import com.nextcloud.talk.users.UserManager
  53. import com.nextcloud.talk.utils.ApiUtils
  54. import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
  55. import com.nextcloud.talk.utils.SecurityUtils
  56. import com.nextcloud.talk.utils.bundle.BundleKeys
  57. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
  58. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
  59. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
  60. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
  61. import io.reactivex.Observer
  62. import io.reactivex.SingleObserver
  63. import io.reactivex.android.schedulers.AndroidSchedulers
  64. import io.reactivex.disposables.Disposable
  65. import io.reactivex.schedulers.Schedulers
  66. import org.parceler.Parcels
  67. import javax.inject.Inject
  68. @AutoInjector(NextcloudTalkApplication::class)
  69. class MainActivity : BaseActivity(), ActionBarProvider {
  70. lateinit var binding: ActivityMainBinding
  71. @Inject
  72. lateinit var ncApi: NcApi
  73. @Inject
  74. lateinit var userManager: UserManager
  75. private var router: Router? = null
  76. @Suppress("Detekt.TooGenericExceptionCaught")
  77. override fun onCreate(savedInstanceState: Bundle?) {
  78. Log.d(TAG, "onCreate: Activity: " + System.identityHashCode(this).toString())
  79. super.onCreate(savedInstanceState)
  80. // Set the default theme to replace the launch screen theme.
  81. setTheme(R.style.AppTheme)
  82. binding = ActivityMainBinding.inflate(layoutInflater)
  83. setContentView(binding.root)
  84. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  85. setSupportActionBar(binding.toolbar)
  86. router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
  87. if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
  88. onNewIntent(intent)
  89. } else if (!router!!.hasRootController()) {
  90. if (!appPreferences.isDbRoomMigrated) {
  91. appPreferences.isDbRoomMigrated = true
  92. }
  93. userManager.users.subscribe(object : SingleObserver<List<User>> {
  94. override fun onSubscribe(d: Disposable) {
  95. // unused atm
  96. }
  97. override fun onSuccess(users: List<User>) {
  98. if (users.isNotEmpty()) {
  99. runOnUiThread {
  100. setDefaultRootController()
  101. }
  102. } else {
  103. runOnUiThread {
  104. launchLoginScreen()
  105. }
  106. }
  107. }
  108. override fun onError(e: Throwable) {
  109. Log.e(TAG, "Error loading existing users", e)
  110. }
  111. })
  112. }
  113. }
  114. private fun launchLoginScreen() {
  115. if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
  116. router!!.pushController(
  117. RouterTransaction.with(
  118. WebViewLoginController(resources.getString(R.string.weblogin_url), false)
  119. )
  120. .pushChangeHandler(HorizontalChangeHandler())
  121. .popChangeHandler(HorizontalChangeHandler())
  122. )
  123. } else {
  124. router!!.setRoot(
  125. RouterTransaction.with(ServerSelectionController())
  126. .pushChangeHandler(HorizontalChangeHandler())
  127. .popChangeHandler(HorizontalChangeHandler())
  128. )
  129. }
  130. }
  131. override fun onStart() {
  132. super.onStart()
  133. Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
  134. logRouterBackStack(router!!)
  135. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  136. lockScreenIfConditionsApply()
  137. }
  138. }
  139. override fun onResume() {
  140. Log.d(TAG, "onResume: Activity: " + System.identityHashCode(this).toString())
  141. super.onResume()
  142. }
  143. override fun onPause() {
  144. Log.d(TAG, "onPause: Activity: " + System.identityHashCode(this).toString())
  145. super.onPause()
  146. }
  147. override fun onStop() {
  148. Log.d(TAG, "onStop: Activity: " + System.identityHashCode(this).toString())
  149. super.onStop()
  150. }
  151. private fun setDefaultRootController() {
  152. router!!.setRoot(
  153. RouterTransaction.with(ConversationsListController(Bundle()))
  154. .pushChangeHandler(HorizontalChangeHandler())
  155. .popChangeHandler(HorizontalChangeHandler())
  156. )
  157. }
  158. fun resetConversationsList() {
  159. userManager.users.subscribe(object : SingleObserver<List<User>> {
  160. override fun onSubscribe(d: Disposable) {
  161. // unused atm
  162. }
  163. override fun onSuccess(users: List<User>) {
  164. if (users.isNotEmpty()) {
  165. runOnUiThread {
  166. setDefaultRootController()
  167. }
  168. }
  169. }
  170. override fun onError(e: Throwable) {
  171. Log.e(TAG, "Error loading existing users", e)
  172. }
  173. })
  174. }
  175. fun openSettings() {
  176. router!!.pushController(
  177. RouterTransaction.with(SettingsController())
  178. .pushChangeHandler(HorizontalChangeHandler())
  179. .popChangeHandler(HorizontalChangeHandler())
  180. )
  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, currentUser?.baseUrl, roomType,
  224. null, userId, null
  225. )
  226. ncApi.createRoom(
  227. credentials,
  228. retrofitBucket.url, retrofitBucket.queryMap
  229. )
  230. .subscribeOn(Schedulers.io())
  231. .observeOn(AndroidSchedulers.mainThread())
  232. .subscribe(object : Observer<RoomOverall> {
  233. override fun onSubscribe(d: Disposable) {
  234. // unused atm
  235. }
  236. override fun onNext(roomOverall: RoomOverall) {
  237. val bundle = Bundle()
  238. bundle.putParcelable(KEY_USER_ENTITY, currentUser)
  239. bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
  240. bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
  241. // FIXME once APIv2 or later is used only, the createRoom already returns all the data
  242. ncApi.getRoom(
  243. credentials,
  244. ApiUtils.getUrlForRoom(
  245. apiVersion,
  246. currentUser?.baseUrl,
  247. roomOverall.ocs!!.data!!.token
  248. )
  249. )
  250. .subscribeOn(Schedulers.io())
  251. .observeOn(AndroidSchedulers.mainThread())
  252. .subscribe(object : Observer<RoomOverall> {
  253. override fun onSubscribe(d: Disposable) {
  254. // unused atm
  255. }
  256. override fun onNext(roomOverall: RoomOverall) {
  257. bundle.putParcelable(
  258. KEY_ACTIVE_CONVERSATION,
  259. Parcels.wrap(roomOverall.ocs!!.data)
  260. )
  261. remapChatController(
  262. router!!, currentUser!!.id!!,
  263. roomOverall.ocs!!.data!!.token!!, bundle, true
  264. )
  265. }
  266. override fun onError(e: Throwable) {
  267. // unused atm
  268. }
  269. override fun onComplete() {
  270. // unused atm
  271. }
  272. })
  273. }
  274. override fun onError(e: Throwable) {
  275. // unused atm
  276. }
  277. override fun onComplete() {
  278. // unused atm
  279. }
  280. })
  281. }
  282. @RequiresApi(api = Build.VERSION_CODES.M)
  283. fun lockScreenIfConditionsApply() {
  284. val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
  285. if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
  286. if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
  287. if (router != null && router!!.getControllerWithTag(LockedController.TAG) == null) {
  288. router!!.pushController(
  289. RouterTransaction.with(LockedController())
  290. .pushChangeHandler(VerticalChangeHandler())
  291. .popChangeHandler(VerticalChangeHandler())
  292. .tag(LockedController.TAG)
  293. )
  294. logRouterBackStack(router!!)
  295. }
  296. }
  297. }
  298. }
  299. override fun onNewIntent(intent: Intent) {
  300. super.onNewIntent(intent)
  301. Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString())
  302. handleActionFromContact(intent)
  303. if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
  304. if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
  305. if (!router!!.hasRootController()) {
  306. setDefaultRootController()
  307. }
  308. val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
  309. intent.extras?.let { callNotificationIntent.putExtras(it) }
  310. startActivity(callNotificationIntent)
  311. } else {
  312. logRouterBackStack(router!!)
  313. remapChatController(
  314. router!!,
  315. intent.getParcelableExtra<User>(KEY_USER_ENTITY)!!.id!!,
  316. intent.getStringExtra(KEY_ROOM_TOKEN)!!,
  317. intent.extras!!,
  318. false,
  319. true
  320. )
  321. logRouterBackStack(router!!)
  322. }
  323. }
  324. }
  325. override fun onBackPressed() {
  326. if (router!!.getControllerWithTag(LockedController.TAG) != null) {
  327. return
  328. }
  329. if (!router!!.handleBack()) {
  330. super.onBackPressed()
  331. }
  332. }
  333. private fun logRouterBackStack(router: Router) {
  334. if (BuildConfig.DEBUG) {
  335. val backstack = router.backstack
  336. var routerTransaction: RouterTransaction?
  337. Log.d(TAG, " backstack size: " + router.backstackSize)
  338. for (i in 0 until router.backstackSize) {
  339. routerTransaction = backstack[i]
  340. Log.d(TAG, " controller: " + routerTransaction.controller)
  341. }
  342. }
  343. }
  344. companion object {
  345. private const val TAG = "MainActivity"
  346. }
  347. }