From 7518fdbcd90fabed24e6765cf879ef9e1b1dc55c Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 3 May 2023 19:25:39 +0200 Subject: [PATCH] fix screen locking feature Since most controllers were replaced by activities, the screen locking was temporarily broken. This commit fixes the screen locking to also work with the activities. Signed-off-by: Marcel Hibbe --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 4 + .../nextcloud/talk/activities/BaseActivity.kt | 25 ++- .../nextcloud/talk/activities/MainActivity.kt | 51 ++--- .../talk/controllers/LockedController.kt | 177 ------------------ .../com/nextcloud/talk/lock/LockedActivity.kt | 153 +++++++++++++++ ...troller_locked.xml => activity_locked.xml} | 1 - 7 files changed, 198 insertions(+), 215 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt create mode 100644 app/src/main/java/com/nextcloud/talk/lock/LockedActivity.kt rename app/src/main/res/layout/{controller_locked.xml => activity_locked.xml} (98%) 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">