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 e4a5ed9ed..9938c3dd5 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -37,6 +37,7 @@ import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.google.android.material.snackbar.Snackbar +import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication @@ -144,14 +145,13 @@ class MainActivity : BaseActivity(), ActionBarProvider { } override fun onStart() { - Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString()) - super.onStart() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - checkIfWeAreSecure() - } + Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString()) + logRouterBackStack(router!!) - handleActionFromContact(intent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + lockScreenIfConditionsApply() + } } override fun onResume() { @@ -324,7 +324,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { } @RequiresApi(api = Build.VERSION_CODES.M) - fun checkIfWeAreSecure() { + fun lockScreenIfConditionsApply() { val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) { if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) { @@ -335,14 +335,15 @@ class MainActivity : BaseActivity(), ActionBarProvider { .popChangeHandler(VerticalChangeHandler()) .tag(LockedController.TAG) ) + logRouterBackStack(router!!) } } } } override fun onNewIntent(intent: Intent) { - Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString()) super.onNewIntent(intent) + Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString()) handleActionFromContact(intent) if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { @@ -353,10 +354,16 @@ class MainActivity : BaseActivity(), ActionBarProvider { intent.extras?.let { callNotificationIntent.putExtras(it) } startActivity(callNotificationIntent) } else { + logRouterBackStack(router!!) remapChatController( - router!!, intent.getParcelableExtra(KEY_USER_ENTITY)!!.id!!, - intent.getStringExtra(KEY_ROOM_TOKEN)!!, intent.extras!!, false, true + router!!, + intent.getParcelableExtra(KEY_USER_ENTITY)!!.id!!, + intent.getStringExtra(KEY_ROOM_TOKEN)!!, + intent.extras!!, + false, + true ) + logRouterBackStack(router!!) } } } @@ -371,6 +378,18 @@ class MainActivity : BaseActivity(), ActionBarProvider { } } + private fun logRouterBackStack(router: Router) { + if (BuildConfig.DEBUG) { + val backstack = router.backstack + var routerTransaction: RouterTransaction? + Log.d(TAG, " backstack size: " + router.backstackSize) + for (i in 0 until router.backstackSize) { + routerTransaction = backstack[i] + Log.d(TAG, " controller: " + routerTransaction.controller) + } + } + } + companion object { private const val TAG = "MainActivity" } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt index 9b9276ed5..e18bdbbd6 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt @@ -73,6 +73,7 @@ class LockedController : BaseController(R.layout.controller_locked) { @RequiresApi(api = Build.VERSION_CODES.M) override fun onAttach(view: View) { super.onAttach(view) + Log.d(TAG, "onAttach") if (activity != null && resources != null) { DisplayUtils.applyColorToStatusBar( activity, @@ -86,6 +87,11 @@ class LockedController : BaseController(R.layout.controller_locked) { checkIfWeAreSecure() } + override fun onDetach(view: View) { + super.onDetach(view) + Log.d(TAG, "onDetach") + } + @RequiresApi(api = Build.VERSION_CODES.M) fun unlock() { checkIfWeAreSecure() @@ -139,14 +145,21 @@ class LockedController : BaseController(R.layout.controller_locked) { 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.popCurrentController() } } } 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) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index 18f78c81f..b288900ee 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -333,30 +333,30 @@ class SettingsController : BaseController(R.layout.controller_settings) { } private fun registerChangeListeners() { - appPreferences!!.registerProxyTypeListener(ProxyTypeChangeListener().also { proxyTypeChangeListener = it }) - appPreferences!!.registerProxyCredentialsListener( + appPreferences.registerProxyTypeListener(ProxyTypeChangeListener().also { proxyTypeChangeListener = it }) + appPreferences.registerProxyCredentialsListener( ProxyCredentialsChangeListener().also { proxyCredentialsChangeListener = it } ) - appPreferences!!.registerScreenSecurityListener( + appPreferences.registerScreenSecurityListener( ScreenSecurityChangeListener().also { screenSecurityChangeListener = it } ) - appPreferences!!.registerScreenLockListener(ScreenLockListener().also { screenLockChangeListener = it }) - appPreferences!!.registerScreenLockTimeoutListener( + appPreferences.registerScreenLockListener(ScreenLockListener().also { screenLockChangeListener = it }) + appPreferences.registerScreenLockTimeoutListener( ScreenLockTimeoutListener().also { screenLockTimeoutChangeListener = it } ) - appPreferences!!.registerThemeChangeListener(ThemeChangeListener().also { themeChangeListener = it }) - appPreferences!!.registerPhoneBookIntegrationChangeListener( + appPreferences.registerThemeChangeListener(ThemeChangeListener().also { themeChangeListener = it }) + appPreferences.registerPhoneBookIntegrationChangeListener( PhoneBookIntegrationChangeListener(this).also { phoneBookIntegrationChangeListener = it } ) - appPreferences!!.registerReadPrivacyChangeListener( + appPreferences.registerReadPrivacyChangeListener( ReadPrivacyChangeListener().also { readPrivacyChangeListener = it } @@ -525,7 +525,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { } private fun setupProxyTypeSettings() { - if (("No proxy" == appPreferences!!.proxyType) || appPreferences!!.proxyType == null) { + if (("No proxy" == appPreferences.proxyType) || appPreferences.proxyType == null) { hideProxySettings() } else { showProxySettings() @@ -533,7 +533,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { } private fun setupProxyCredentialSettings() { - if (appPreferences!!.proxyCredentials) { + if (appPreferences.proxyCredentials) { showProxyCredentials() } else { hideProxyCredentials() @@ -655,16 +655,16 @@ class SettingsController : BaseController(R.layout.controller_settings) { private fun setupCheckables() { (binding.settingsScreenSecurity.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isScreenSecured + appPreferences.isScreenSecured if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { (binding.settingsIncognitoKeyboard.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isKeyboardIncognito + appPreferences.isKeyboardIncognito } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { (binding.settingsIncognitoKeyboard.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isKeyboardIncognito + appPreferences.isKeyboardIncognito } if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) { @@ -675,19 +675,19 @@ class SettingsController : BaseController(R.layout.controller_settings) { } (binding.settingsPhoneBookIntegration.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isPhoneBookIntegrationEnabled + appPreferences.isPhoneBookIntegrationEnabled } private fun setupScreenLockSetting() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val keyguardManager = context!!.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager if (keyguardManager.isKeyguardSecure) { binding.settingsScreenLock.isEnabled = true binding.settingsScreenLockTimeout.isEnabled = true (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isScreenLocked - binding.settingsScreenLockTimeout.isEnabled = appPreferences!!.isScreenLocked - if (appPreferences!!.isScreenLocked) { + appPreferences.isScreenLocked + binding.settingsScreenLockTimeout.isEnabled = appPreferences.isScreenLocked + if (appPreferences.isScreenLocked) { binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA } else { binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA @@ -696,8 +696,8 @@ class SettingsController : BaseController(R.layout.controller_settings) { } else { binding.settingsScreenLock.isEnabled = false binding.settingsScreenLockTimeout.isEnabled = false - appPreferences!!.removeScreenLock() - appPreferences!!.removeScreenLockTimeout() + appPreferences.removeScreenLock() + appPreferences.removeScreenLockTimeout() (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = false binding.settingsScreenLock.alpha = DISABLED_ALPHA binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA @@ -792,12 +792,12 @@ class SettingsController : BaseController(R.layout.controller_settings) { .enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java).build()) checkForPhoneNumber() } else { - appPreferences!!.setPhoneBookIntegration(false) + appPreferences.setPhoneBookIntegration(false) (binding.settingsPhoneBookIntegration.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences!!.isPhoneBookIntegrationEnabled + appPreferences.isPhoneBookIntegrationEnabled Toast.makeText( context, - context!!.resources.getString(R.string.no_phone_book_integration_due_to_permissions), + context.resources.getString(R.string.no_phone_book_integration_due_to_permissions), Toast.LENGTH_LONG ).show() } @@ -806,7 +806,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener { override fun onChanged(newValue: String?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - SecurityUtils.createKey(appPreferences!!.screenLockTimeout) + SecurityUtils.createKey(appPreferences.screenLockTimeout) } } } @@ -949,8 +949,8 @@ class SettingsController : BaseController(R.layout.controller_settings) { .setTitle(R.string.nc_settings_phone_book_integration_phone_number_dialog_title) .setMessage(R.string.nc_settings_phone_book_integration_phone_number_dialog_description) .setView(phoneNumberLayoutWrapper) - .setPositiveButton(context!!.resources.getString(R.string.nc_common_set), null) - .setNegativeButton(context!!.resources.getString(R.string.nc_common_skip), null) + .setPositiveButton(context.resources.getString(R.string.nc_common_set), null) + .setNegativeButton(context.resources.getString(R.string.nc_common_skip), null) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(phoneNumberInputLayout.context, dialogBuilder) @@ -994,13 +994,13 @@ class SettingsController : BaseController(R.layout.controller_settings) { dialog.dismiss() Toast.makeText( context, - context!!.resources.getString( + context.resources.getString( R.string.nc_settings_phone_book_integration_phone_number_dialog_success ), Toast.LENGTH_LONG ).show() } else { - textInputLayout.helperText = context!!.resources.getString( + textInputLayout.helperText = context.resources.getString( R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid ) Log.d(TAG, "failed to set phoneNumber. statusCode=$statusCode") @@ -1008,7 +1008,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { } override fun onError(e: Throwable) { - textInputLayout.helperText = context!!.resources.getString( + textInputLayout.helperText = context.resources.getString( R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid ) Log.e(TAG, "setPhoneNumber error", e) @@ -1041,7 +1041,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { } override fun onError(e: Throwable) { - appPreferences!!.setReadPrivacy(!newValue) + appPreferences.setReadPrivacy(!newValue) (binding.settingsReadPrivacy.findViewById(R.id.mp_checkable) as Checkable).isChecked = !newValue } diff --git a/app/src/main/java/com/nextcloud/talk/utils/ConductorRemapping.kt b/app/src/main/java/com/nextcloud/talk/utils/ConductorRemapping.kt index 1f95bdca2..d8d7fb89c 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ConductorRemapping.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ConductorRemapping.kt @@ -21,15 +21,19 @@ package com.nextcloud.talk.utils import android.os.Bundle +import android.util.Log import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler import com.nextcloud.talk.controllers.ChatController import com.nextcloud.talk.controllers.ConversationsListController +import com.nextcloud.talk.controllers.LockedController object ConductorRemapping { + private val TAG = ConductorRemapping::class.simpleName + fun remapChatController( router: Router, internalUserId: Long, @@ -48,20 +52,10 @@ object ConductorRemapping { replaceTop: Boolean, pushImmediately: Boolean ) { - val tag = "$internalUserId@$roomTokenOrId" - if (router.getControllerWithTag(tag) != null) { - val backstack = router.backstack - var routerTransaction: RouterTransaction? = null - for (i in 0 until router.backstackSize) { - if (tag == backstack[i].tag()) { - routerTransaction = backstack[i] - backstack.remove(routerTransaction) - break - } - } + val chatControllerTag = "$internalUserId@$roomTokenOrId" - backstack.add(routerTransaction) - router.setBackstack(backstack, HorizontalChangeHandler()) + if (router.getControllerWithTag(chatControllerTag) != null) { + moveControllerToTop(router, chatControllerTag) } else { val pushChangeHandler = if (pushImmediately) { SimpleSwapChangeHandler() @@ -70,29 +64,55 @@ object ConductorRemapping { } if (!replaceTop) { if (!router.hasRootController()) { + Log.d(TAG, "router has no RootController. creating backstack with ConversationsListController") val newBackstack = listOf( RouterTransaction.with(ConversationsListController(Bundle())) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()), RouterTransaction.with(ChatController(bundle)) .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()).tag(tag) + .popChangeHandler(HorizontalChangeHandler()).tag(chatControllerTag) ) router.setBackstack(newBackstack, SimpleSwapChangeHandler()) } else { + Log.d(TAG, "router has RootController. pushing ChatController") router.pushController( RouterTransaction.with(ChatController(bundle)) .pushChangeHandler(pushChangeHandler) - .popChangeHandler(HorizontalChangeHandler()).tag(tag) + .popChangeHandler(HorizontalChangeHandler()).tag(chatControllerTag) ) } } else { + Log.d(TAG, "ChatController replace topController") + router.replaceTopController( RouterTransaction.with(ChatController(bundle)) .pushChangeHandler(pushChangeHandler) - .popChangeHandler(HorizontalChangeHandler()).tag(tag) + .popChangeHandler(HorizontalChangeHandler()).tag(chatControllerTag) ) } } + + if (router.getControllerWithTag(LockedController.TAG) != null) { + moveControllerToTop(router, LockedController.TAG) + } + } + + private fun moveControllerToTop(router: Router, controllerTag: String) { + Log.d(TAG, "moving $controllerTag to top...") + val backstack = router.backstack + var routerTransaction: RouterTransaction? = null + for (i in 0 until router.backstackSize) { + if (controllerTag == backstack[i].tag()) { + routerTransaction = backstack[i] + backstack.remove(routerTransaction) + Log.d(TAG, "removed controller: " + routerTransaction.controller) + break + } + } + + backstack.add(routerTransaction) + Log.d(TAG, "added controller to top: " + routerTransaction!!.controller) + router.setBackstack(backstack, HorizontalChangeHandler()) } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java index 2542086d6..5420f44af 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java @@ -27,18 +27,32 @@ import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.UserNotAuthenticatedException; import android.util.Log; -import androidx.annotation.RequiresApi; -import androidx.biometric.BiometricPrompt; + import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; -import javax.crypto.*; import java.io.IOException; -import java.security.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +import androidx.annotation.RequiresApi; +import androidx.biometric.BiometricPrompt; + public class SecurityUtils { private static final String TAG = "SecurityUtils"; private static final String CREDENTIALS_KEY = "KEY_CREDENTIALS"; @@ -68,8 +82,8 @@ public class SecurityUtils { return false; } catch (KeyPermanentlyInvalidatedException e) { // This happens if the lock screen has been disabled or reset after the key was - // generated after the key was generated. - // Shouldnt really happen because we regenerate the key every time an activity + // generated. + // Shouldn't really happen because we regenerate the key every time an activity // is created, but oh well // Create key, and attempt again createKey(screenLockTimeout); diff --git a/app/src/main/res/layout/controller_settings.xml b/app/src/main/res/layout/controller_settings.xml index 7c239a303..c10fe7e80 100644 --- a/app/src/main/res/layout/controller_settings.xml +++ b/app/src/main/res/layout/controller_settings.xml @@ -197,6 +197,14 @@ apc:cardElevation="0dp" apc:mpc_title="@string/nc_settings_privacy"> + + - -