Jelajahi Sumber

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

fix screen locking feature
Marcel Hibbe 1 tahun lalu
induk
melakukan
21ba5d87f0

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