diff --git a/app/build.gradle b/app/build.gradle
index 83b1440b7..bf8918f10 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -194,6 +194,8 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${lifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${lifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycleVersion}"
+ implementation "androidx.lifecycle:lifecycle-process:${lifecycleVersion}"
+ implementation "androidx.lifecycle:lifecycle-common:${lifecycleVersion}"
implementation 'androidx.biometric:biometric:1.1.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 47a850c85..cee5b009c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -234,6 +234,10 @@
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt
index 7cb659aa0..05792f941 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt
@@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @author Mario Danic
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
* Copyright (C) 2017-2018 Mario Danic
*
* This program is free software: you can redistribute it and/or modify
@@ -37,7 +39,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.events.CertificateEvent
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.ssl.TrustManager
import org.greenrobot.eventbus.EventBus
@@ -78,6 +79,11 @@ open class BaseActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
}
+ public override fun onStart() {
+ super.onStart()
+ eventBus.register(this)
+ }
+
public override fun onResume() {
super.onResume()
@@ -86,10 +92,11 @@ open class BaseActivity : AppCompatActivity() {
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
+ }
- if (appPreferences.isScreenLocked) {
- SecurityUtils.createKey(appPreferences.screenLockTimeout)
- }
+ public override fun onStop() {
+ super.onStop()
+ eventBus.unregister(this)
}
fun setupSystemColors() {
@@ -183,16 +190,6 @@ open class BaseActivity : AppCompatActivity() {
showCertificateDialog(event.x509Certificate, event.magicTrustManager, event.sslErrorHandler)
}
- public override fun onStart() {
- super.onStart()
- eventBus.register(this)
- }
-
- public override fun onStop() {
- super.onStop()
- eventBus.unregister(this)
- }
-
companion object {
private val TAG = "BaseActivity"
}
diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
index 03084f125..151f11180 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
@@ -3,6 +3,8 @@
*
* @author Mario Danic
* @author Andy Scherzinger
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
* Copyright (C) 2021 Andy Scherzinger (infoi@andy-scherzinger.de)
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
@@ -28,6 +30,9 @@ import android.os.Bundle
import android.provider.ContactsContract
import android.text.TextUtils
import android.util.Log
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
import autodagger.AutoInjector
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
@@ -40,13 +45,13 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
-import com.nextcloud.talk.controllers.LockedController
import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.WebViewLoginController
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityMainBinding
+import com.nextcloud.talk.lock.LockedActivity
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
@@ -82,6 +87,13 @@ class MainActivity : BaseActivity(), ActionBarProvider {
Log.d(TAG, "onCreate: Activity: " + System.identityHashCode(this).toString())
super.onCreate(savedInstanceState)
+
+ ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ lockScreenIfConditionsApply()
+ }
+ })
+
// Set the default theme to replace the launch screen theme.
setTheme(R.style.AppTheme)
binding = ActivityMainBinding.inflate(layoutInflater)
@@ -126,6 +138,16 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
}
+ fun lockScreenIfConditionsApply() {
+ val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
+ if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
+ if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
+ val lockIntent = Intent(context, LockedActivity::class.java)
+ startActivity(lockIntent)
+ }
+ }
+ }
+
private fun launchLoginScreen() {
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
router!!.pushController(
@@ -145,15 +167,18 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
override fun onStart() {
- super.onStart()
Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
+ super.onStart()
logRouterBackStack(router!!)
- lockScreenIfConditionsApply()
}
override fun onResume() {
Log.d(TAG, "onResume: Activity: " + System.identityHashCode(this).toString())
super.onResume()
+
+ if (appPreferences.isScreenLocked) {
+ SecurityUtils.createKey(appPreferences.screenLockTimeout)
+ }
}
override fun onPause() {
@@ -295,23 +320,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
})
}
- fun lockScreenIfConditionsApply() {
- val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
- if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
- if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
- if (router != null && router!!.getControllerWithTag(LockedController.TAG) == null) {
- router!!.pushController(
- RouterTransaction.with(LockedController())
- .pushChangeHandler(VerticalChangeHandler())
- .popChangeHandler(VerticalChangeHandler())
- .tag(LockedController.TAG)
- )
- logRouterBackStack(router!!)
- }
- }
- }
- }
-
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString())
@@ -346,9 +354,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
override fun onBackPressed() {
- if (router!!.getControllerWithTag(LockedController.TAG) != null) {
- return
- }
if (!router!!.handleBack()) {
super.onBackPressed()
}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt
deleted file mode 100644
index 3f995a926..000000000
--- a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2021 Andy Scherzinger
- * Copyright (C) 2017-2018 Mario Danic
- *
- * 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 .
- */
-package com.nextcloud.talk.controllers
-
-import android.app.Activity
-import android.app.KeyguardManager
-import android.content.Context
-import android.content.Intent
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.View
-import androidx.biometric.BiometricPrompt
-import androidx.biometric.BiometricPrompt.PromptInfo
-import androidx.core.content.res.ResourcesCompat
-import androidx.fragment.app.FragmentActivity
-import autodagger.AutoInjector
-import com.nextcloud.talk.R
-import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.BaseController
-import com.nextcloud.talk.controllers.util.viewBinding
-import com.nextcloud.talk.databinding.ControllerLockedBinding
-import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.SecurityUtils
-import java.util.concurrent.Executor
-import java.util.concurrent.Executors
-
-@AutoInjector(NextcloudTalkApplication::class)
-class LockedController : BaseController(R.layout.controller_locked) {
- private val binding: ControllerLockedBinding? by viewBinding(ControllerLockedBinding::bind)
-
- override val appBarLayoutType: AppBarLayoutType
- get() = AppBarLayoutType.EMPTY
-
- companion object {
- const val TAG = "LockedController"
- private const val REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112
- }
-
- override fun onViewBound(view: View) {
- super.onViewBound(view)
- sharedApplication!!.componentApplication.inject(this)
- binding?.unlockContainer?.setOnClickListener {
- unlock()
- }
- }
-
- override fun onAttach(view: View) {
- super.onAttach(view)
- Log.d(TAG, "onAttach")
- if (activity != null && resources != null) {
- DisplayUtils.applyColorToStatusBar(
- activity,
- ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
- )
- DisplayUtils.applyColorToNavigationBar(
- activity!!.window,
- ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
- )
- }
- checkIfWeAreSecure()
- }
-
- override fun onDetach(view: View) {
- super.onDetach(view)
- Log.d(TAG, "onDetach")
- }
-
- fun unlock() {
- checkIfWeAreSecure()
- }
-
- private fun showBiometricDialog() {
- val context: Context? = activity
- if (context != null) {
- val promptInfo = PromptInfo.Builder()
- .setTitle(
- String.format(
- context.getString(R.string.nc_biometric_unlock),
- context.getString(R.string.nc_app_product_name)
- )
- )
- .setNegativeButtonText(context.getString(R.string.nc_cancel))
- .build()
- val executor: Executor = Executors.newSingleThreadExecutor()
- val biometricPrompt = BiometricPrompt(
- (context as FragmentActivity?)!!,
- executor,
- object : BiometricPrompt.AuthenticationCallback() {
- override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
- super.onAuthenticationSucceeded(result)
- Log.d(TAG, "Fingerprint recognised successfully")
- Handler(Looper.getMainLooper()).post { router.popCurrentController() }
- }
-
- override fun onAuthenticationFailed() {
- super.onAuthenticationFailed()
- Log.d(TAG, "Fingerprint not recognised")
- }
-
- override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
- super.onAuthenticationError(errorCode, errString)
- showAuthenticationScreen()
- }
- }
- )
- val cryptoObject = SecurityUtils.getCryptoObject()
- if (cryptoObject != null) {
- biometricPrompt.authenticate(promptInfo, cryptoObject)
- } else {
- biometricPrompt.authenticate(promptInfo)
- }
- }
- }
-
- private fun checkIfWeAreSecure() {
- val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
- if (keyguardManager?.isKeyguardSecure == true && appPreferences.isScreenLocked) {
- if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
- Log.d(TAG, "showBiometricDialog because 'we are NOT authenticated'...")
- showBiometricDialog()
- } else {
- Log.d(
- TAG,
- "popCurrentController because 'we are authenticated'. backstacksize= " +
- router.backstack.size
- )
- router.popToRoot()
- }
- }
- }
-
- private fun showAuthenticationScreen() {
- Log.d(TAG, "showAuthenticationScreen")
- val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
- val intent = keyguardManager?.createConfirmDeviceCredentialIntent(null, null)
- if (intent != null) {
- startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS)
- }
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
- if (resultCode == Activity.RESULT_OK) {
- if (
- SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)
- ) {
- Log.d(TAG, "All went well, dismiss locked controller")
- router.popCurrentController()
- }
- } else {
- Log.d(TAG, "Authorization failed")
- }
- }
- }
-}
diff --git a/app/src/main/java/com/nextcloud/talk/lock/LockedActivity.kt b/app/src/main/java/com/nextcloud/talk/lock/LockedActivity.kt
new file mode 100644
index 000000000..86d49755a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/lock/LockedActivity.kt
@@ -0,0 +1,153 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2017-2018 Mario Danic
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.lock
+
+import android.app.Activity
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.biometric.BiometricPrompt
+import autodagger.AutoInjector
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.databinding.ActivityLockedBinding
+import com.nextcloud.talk.utils.SecurityUtils
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class LockedActivity : AppCompatActivity() {
+
+ @Inject
+ lateinit var context: Context
+
+ @Inject
+ lateinit var appPreferences: AppPreferences
+
+ private lateinit var binding: ActivityLockedBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
+
+ binding = ActivityLockedBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ binding.unlockContainer.setOnClickListener {
+ checkIfWeAreSecure()
+ }
+ checkIfWeAreSecure()
+ }
+
+ private fun checkIfWeAreSecure() {
+ val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
+ if (keyguardManager?.isKeyguardSecure == true && appPreferences.isScreenLocked) {
+ if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
+ Log.d(TAG, "showBiometricDialog because 'we are NOT authenticated'...")
+ showBiometricDialog()
+ } else {
+ finish()
+ }
+ }
+ }
+
+ private fun showBiometricDialog() {
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(
+ String.format(
+ context.getString(R.string.nc_biometric_unlock),
+ context.getString(R.string.nc_app_product_name)
+ )
+ )
+ .setNegativeButtonText(context.getString(R.string.nc_cancel))
+ .build()
+ val executor: Executor = Executors.newSingleThreadExecutor()
+ val biometricPrompt = BiometricPrompt(
+ this,
+ executor,
+ object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ Log.d(TAG, "Fingerprint recognised successfully")
+ finish()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ Log.d(TAG, "Fingerprint not recognised")
+ }
+
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ showAuthenticationScreen()
+ }
+ }
+ )
+ val cryptoObject = SecurityUtils.getCryptoObject()
+ if (cryptoObject != null) {
+ biometricPrompt.authenticate(promptInfo, cryptoObject)
+ } else {
+ biometricPrompt.authenticate(promptInfo)
+ }
+ }
+
+ private fun showAuthenticationScreen() {
+ Log.d(TAG, "showAuthenticationScreen")
+ val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
+ val intent = keyguardManager?.createConfirmDeviceCredentialIntent(null, null)
+ if (intent != null) {
+ startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS)
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
+ if (resultCode == Activity.RESULT_OK) {
+ if (
+ SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)
+ ) {
+ finish()
+ }
+ } else {
+ Log.d(TAG, "Authorization failed")
+ }
+ }
+ }
+
+ companion object {
+ private val TAG = LockedActivity::class.java.simpleName
+ private const val REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112
+ }
+}
diff --git a/app/src/main/res/layout/controller_locked.xml b/app/src/main/res/layout/activity_locked.xml
similarity index 98%
rename from app/src/main/res/layout/controller_locked.xml
rename to app/src/main/res/layout/activity_locked.xml
index 9901d5520..e80a1dfd5 100644
--- a/app/src/main/res/layout/controller_locked.xml
+++ b/app/src/main/res/layout/activity_locked.xml
@@ -25,7 +25,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
- android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="vertical">