Merge pull request #2996 from nextcloud/bugfix/2958/fixLockedScreen

fix screen locking feature
This commit is contained in:
Marcel Hibbe 2023-05-04 11:13:27 +02:00 committed by GitHub
commit 21ba5d87f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 215 deletions

View File

@ -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'

View File

@ -234,6 +234,10 @@
</intent-filter>
</activity>
<activity
android:name=".lock.LockedActivity"
android:theme="@style/AppTheme" />
<receiver
android:name=".receivers.PackageReplacedReceiver"
android:exported="false">

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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"
}

View File

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* 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()
}

View File

@ -1,177 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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")
}
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View File

@ -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">