Skip to content

Commit

Permalink
feat(AppLock): Adjust appLock to be handled correctly with other feat…
Browse files Browse the repository at this point in the history
…ures (WPB-4691) (#2363)
  • Loading branch information
ohassine committed Nov 2, 2023
1 parent 730d9e6 commit 0584961
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 102 deletions.
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -77,6 +77,14 @@
android:theme="@style/AppTheme.SplashScreen"
tools:replace="android:allowBackup,android:supportsRtl">

<activity
android:name=".ui.AppLockActivity"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/AppTheme" />

<activity
android:name=".ui.WireActivity"
android:exported="true"
Expand Down
Expand Up @@ -22,6 +22,7 @@ import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.ERROR_LOCKOUT
import androidx.biometric.BiometricPrompt.ERROR_NEGATIVE_BUTTON
import androidx.core.content.ContextCompat
import com.wire.android.R
Expand All @@ -34,7 +35,8 @@ object BiometricPromptUtils {
activity: AppCompatActivity,
onSuccess: () -> Unit,
onCancel: () -> Unit,
onRequestPasscode: () -> Unit
onRequestPasscode: () -> Unit,
onTooManyFailedAttempts: () -> Unit
): BiometricPrompt {
val executor = ContextCompat.getMainExecutor(activity)

Expand All @@ -43,10 +45,10 @@ object BiometricPromptUtils {
override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) {
super.onAuthenticationError(errorCode, errorString)
appLogger.i("$TAG errorCode is $errorCode and errorString is: $errorString")
if (errorCode == ERROR_NEGATIVE_BUTTON || errorCode == BiometricPrompt.ERROR_LOCKOUT) {
onRequestPasscode()
} else {
onCancel()
when (errorCode) {
ERROR_NEGATIVE_BUTTON -> onRequestPasscode()
ERROR_LOCKOUT -> onTooManyFailedAttempts()
else -> onCancel()
}
}

Expand Down Expand Up @@ -76,7 +78,8 @@ object BiometricPromptUtils {
fun AppCompatActivity.showBiometricPrompt(
onSuccess: () -> Unit,
onCancel: () -> Unit,
onRequestPasscode: () -> Unit
onRequestPasscode: () -> Unit,
onTooManyFailedAttempts: () -> Unit
) {
val canAuthenticate = BiometricManager.from(this)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
Expand All @@ -86,7 +89,8 @@ fun AppCompatActivity.showBiometricPrompt(
activity = this,
onSuccess = onSuccess,
onCancel = onCancel,
onRequestPasscode = onRequestPasscode
onRequestPasscode = onRequestPasscode,
onTooManyFailedAttempts = onTooManyFailedAttempts
)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo)
Expand Down
Expand Up @@ -37,6 +37,7 @@ import androidx.core.text.toSpannable
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.notification.NotificationConstants.getConversationNotificationId
import com.wire.android.ui.home.appLock.LockCodeTimeManager
import com.wire.android.util.toBitmap
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
Expand All @@ -51,7 +52,8 @@ class MessageNotificationManager
@Inject constructor(
private val context: Context,
private val notificationManagerCompat: NotificationManagerCompat,
private val notificationManager: NotificationManager
private val notificationManager: NotificationManager,
private val lockCodeTimeManager: LockCodeTimeManager
) {

fun handleNotification(newNotifications: List<LocalNotification>, userId: QualifiedID, userName: String) {
Expand Down Expand Up @@ -201,8 +203,9 @@ class MessageNotificationManager
}

is NotificationMessage.Comment -> {
val isAppLocked = lockCodeTimeManager.isAppLocked()
setContentIntent(messagePendingIntent(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString, isAppLocked))
}

is NotificationMessage.Knock -> {
Expand All @@ -211,13 +214,15 @@ class MessageNotificationManager
}

is NotificationMessage.Text -> {
val isAppLocked = lockCodeTimeManager.isAppLocked()
setContentIntent(messagePendingIntent(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString, isAppLocked))
}

is NotificationMessage.ObfuscatedMessage -> {
val isAppLocked = lockCodeTimeManager.isAppLocked()
setContentIntent(messagePendingIntent(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString, isAppLocked))
}

is NotificationMessage.ObfuscatedKnock -> {
Expand All @@ -226,8 +231,9 @@ class MessageNotificationManager
}

null -> {
val isAppLocked = lockCodeTimeManager.isAppLocked()
setContentIntent(messagePendingIntent(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString))
addAction(getActionReply(context, conversation.id, userIdString, isAppLocked))
}
}
}
Expand Down Expand Up @@ -470,7 +476,7 @@ class MessageNotificationManager

val notification = setUpNotificationBuilder(context, userId).apply {
setContentIntent(messagePendingIntent(context, conversationId, userIdString))
addAction(getActionReply(context, conversationId, userIdString))
addAction(getActionReply(context, conversationId, userIdString, false))

setWhen(System.currentTimeMillis())

Expand Down
Expand Up @@ -30,15 +30,25 @@ import com.wire.android.R
fun getActionFromOldOne(oldAction: Notification.Action) =
NotificationCompat.Action.Builder(null, oldAction.title, oldAction.actionIntent).build()

fun getActionReply(context: Context, conversationId: String, userId: String?): NotificationCompat.Action {
val resultPendingIntent = replyMessagePendingIntent(context, conversationId, userId)
fun getActionReply(
context: Context,
conversationId: String,
userId: String?,
isAppLocked: Boolean
): NotificationCompat.Action {
return if (isAppLocked) {
val resultPendingIntent = messagePendingIntent(context, conversationId, userId)
NotificationCompat.Action.Builder(null, context.getString(R.string.notification_action_reply), resultPendingIntent)
.build()
} else {
val resultPendingIntent = replyMessagePendingIntent(context, conversationId, userId)
val remoteInput = RemoteInput.Builder(NotificationConstants.KEY_TEXT_REPLY).build()

val remoteInput = RemoteInput.Builder(NotificationConstants.KEY_TEXT_REPLY).build()

return NotificationCompat.Action.Builder(null, context.getString(R.string.notification_action_reply), resultPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build()
NotificationCompat.Action.Builder(null, context.getString(R.string.notification_action_reply), resultPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build()
}
}

fun getOpenIncomingCallAction(context: Context, conversationId: String, userId: String) = getAction(
Expand Down
62 changes: 62 additions & 0 deletions app/src/main/kotlin/com/wire/android/ui/AppLockActivity.kt
@@ -0,0 +1,62 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* 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.wire.android.ui

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.core.view.WindowCompat
import com.wire.android.appLogger
import com.wire.android.navigation.NavigationGraph
import com.wire.android.navigation.rememberNavigator
import com.wire.android.ui.destinations.AppUnlockWithBiometricsScreenDestination
import com.wire.android.ui.destinations.EnterLockCodeScreenDestination
import com.wire.android.ui.theme.WireTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class AppLockActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
WireTheme {
val canAuthenticateWithBiometrics = BiometricManager
.from(this)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)

val navigator = rememberNavigator(this@AppLockActivity::finish)

val startDestination =
if (canAuthenticateWithBiometrics == BiometricManager.BIOMETRIC_SUCCESS) {
appLogger.i("appLock: requesting app Unlock with biometrics")
AppUnlockWithBiometricsScreenDestination
} else {
appLogger.i("appLock: requesting app Unlock with passcode")
EnterLockCodeScreenDestination
}

NavigationGraph(
navigator = navigator,
startDestination = startDestination
)
}
}
}
}
43 changes: 15 additions & 28 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Expand Up @@ -30,8 +30,6 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.SnackbarHostState
Expand Down Expand Up @@ -71,9 +69,7 @@ import com.wire.android.ui.calling.ProximitySensorManager
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.topappbar.CommonTopAppBar
import com.wire.android.ui.common.topappbar.CommonTopAppBarViewModel
import com.wire.android.ui.destinations.AppUnlockWithBiometricsScreenDestination
import com.wire.android.ui.destinations.ConversationScreenDestination
import com.wire.android.ui.destinations.EnterLockCodeScreenDestination
import com.wire.android.ui.destinations.HomeScreenDestination
import com.wire.android.ui.destinations.ImportMediaScreenDestination
import com.wire.android.ui.destinations.IncomingCallScreenDestination
Expand All @@ -100,7 +96,7 @@ import com.wire.android.util.ui.updateScreenSettings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand Down Expand Up @@ -196,7 +192,6 @@ class WireActivity : AppCompatActivity() {
setUpNavigation(navigator.navController, onComplete)
isLoaded = true
handleScreenshotCensoring()
handleAppLock(navigator::navigate)
handleDialogs(navigator::navigate)
}
}
Expand Down Expand Up @@ -250,28 +245,6 @@ class WireActivity : AppCompatActivity() {
}
}

@Composable
private fun handleAppLock(navigate: (NavigationCommand) -> Unit) {
LaunchedEffect(Unit) {
lifecycleScope.launch {
// Listen to one flow in a lifecycle-aware manner using flowWithLifecycle
lockCodeTimeManager.isLocked()
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.filter { it }
.collectLatest {
val canAuthenticateWithBiometrics = BiometricManager
.from(this@WireActivity)
.canAuthenticate(BIOMETRIC_STRONG)
if (canAuthenticateWithBiometrics == BiometricManager.BIOMETRIC_SUCCESS) {
navigate(NavigationCommand(AppUnlockWithBiometricsScreenDestination, BackStackMode.UPDATE_EXISTED))
} else {
navigate(NavigationCommand(EnterLockCodeScreenDestination, BackStackMode.UPDATE_EXISTED))
}
}
}
}
}

@Composable
private fun handleDialogs(navigate: (NavigationCommand) -> Unit) {
featureFlagNotificationViewModel.loadInitialSync()
Expand Down Expand Up @@ -355,6 +328,20 @@ class WireActivity : AppCompatActivity() {

override fun onResume() {
super.onResume()

lifecycleScope.launch {
lockCodeTimeManager.observeAppLock()
// Listen to one flow in a lifecycle-aware manner using flowWithLifecycle
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.first().let {
if (it) {
startActivity(
Intent(this@WireActivity, AppLockActivity::class.java)
)
}
}
}

proximitySensorManager.registerListener()
}

Expand Down
Expand Up @@ -54,6 +54,7 @@ import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
@Composable
fun WireScaffold(
modifier: Modifier = Modifier,
snackbarHost: @Composable () -> Unit = { WireScaffoldSnackbarHost() },
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
Expand All @@ -68,18 +69,7 @@ fun WireScaffold(
.systemBarsPadding(),
topBar = topBar,
bottomBar = bottomBar,
snackbarHost = {
SnackbarHost(
hostState = LocalSnackbarHostState.current,
snackbar = { data ->
SwipeableSnackbar(
hostState = LocalSnackbarHostState.current,
data = data,
onDismiss = { data.dismiss() }
)
}
)
},
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
Expand All @@ -88,3 +78,17 @@ fun WireScaffold(
content = content
)
}

@Composable
private fun WireScaffoldSnackbarHost() {
SnackbarHost(
hostState = LocalSnackbarHostState.current,
snackbar = { data ->
SwipeableSnackbar(
hostState = LocalSnackbarHostState.current,
data = data,
onDismiss = { data.dismiss() }
)
}
)
}

0 comments on commit 0584961

Please sign in to comment.