Browse Source

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 <dev@mhibbe.de>
Marcel Hibbe 1 year ago
parent
commit
7518fdbcd9

+ 2 - 0
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'
 

+ 4 - 0
app/src/main/AndroidManifest.xml

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

+ 11 - 14
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 <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"
     }

+ 28 - 23
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 <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()
         }

+ 0 - 177
app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt

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

+ 153 - 0
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 <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
+    }
+}

+ 0 - 1
app/src/main/res/layout/controller_locked.xml → 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">