Block screen recording during registration by applying FLAG_SECURE.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
Alex Hart
2026-05-05 15:31:35 -03:00
committed by Greyson Parrelli
parent d91f130238
commit 370fca3c89
6 changed files with 59 additions and 11 deletions
@@ -43,6 +43,7 @@ import org.signal.core.models.AccountEntropyPool
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.horizontalGutters
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
import org.thoughtcrime.securesms.fonts.MonoTypeface
import org.thoughtcrime.securesms.registration.ui.restore.BackupKeyVisualTransformation
import org.thoughtcrime.securesms.registration.ui.restore.attachBackupKeyAutoFillHelper
@@ -59,6 +60,8 @@ fun EnterKeyScreen(
captionContent: @Composable () -> Unit,
seeKeyButton: @Composable () -> Unit
) {
TemporaryScreenshotSecurity.bind()
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
@@ -59,6 +59,7 @@ import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.Util
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyCredentialManagerHandler
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState
import org.thoughtcrime.securesms.fonts.MonoTypeface
@@ -133,6 +134,8 @@ fun MessageBackupsKeyRecordScreen(
mode: MessageBackupsKeyRecordMode = MessageBackupsKeyRecordMode.Next(onNextClick = {}),
notifyKeyIsSameAsOnDeviceBackupKey: Boolean = false
) {
TemporaryScreenshotSecurity.bind()
val snackbarHostState = remember { SnackbarHostState() }
val backupKeyString = remember(backupKey) {
backupKey.chunked(4).joinToString(" ")
@@ -5,18 +5,41 @@
package org.thoughtcrime.securesms.components
import android.view.Window
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.util.WeakHashMap
/**
* Applies temporary screenshot security for the given component lifecycle.
*
* Multiple callers can request security on the same window concurrently; the
* flag is only cleared once every caller has released its hold.
*/
object TemporaryScreenshotSecurity {
private val activeHolds = WeakHashMap<Window, Int>()
@Composable
fun bind() {
val activity = LocalActivity.current as? ComponentActivity ?: return
DisposableEffect(activity) {
acquire(activity)
onDispose {
release(activity)
}
}
}
@JvmStatic
fun bindToViewLifecycleOwner(fragment: Fragment) {
val observer = LifecycleObserver { fragment.requireActivity() }
@@ -31,21 +54,37 @@ object TemporaryScreenshotSecurity {
activity.lifecycle.addObserver(observer)
}
private fun acquire(activity: ComponentActivity) {
val window = activity.window
val previous = activeHolds[window] ?: 0
activeHolds[window] = previous + 1
if (previous == 0 && !TextSecurePreferences.isScreenSecurityEnabled(activity)) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
private fun release(activity: ComponentActivity) {
val window = activity.window
val next = ((activeHolds[window] ?: 0) - 1).coerceAtLeast(0)
if (next == 0) {
activeHolds.remove(window)
if (!TextSecurePreferences.isScreenSecurityEnabled(activity)) {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
} else {
activeHolds[window] = next
}
}
private class LifecycleObserver(
private val activityProvider: () -> ComponentActivity
) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
val activity = activityProvider()
if (!TextSecurePreferences.isScreenSecurityEnabled(activity)) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
acquire(activityProvider())
}
override fun onPause(owner: LifecycleOwner) {
val activity = activityProvider()
if (!TextSecurePreferences.isScreenSecurityEnabled(activity)) {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
release(activityProvider())
}
}
}
@@ -12,7 +12,6 @@ import androidx.activity.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.ActivityNavigator
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BaseActivity
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
@@ -26,8 +25,6 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
*/
class RegistrationActivity : BaseActivity() {
private val TAG = Log.tag(RegistrationActivity::class.java)
private val dynamicTheme = DynamicNoActionBarTheme()
val sharedViewModel: RegistrationViewModel by viewModels()
@@ -57,6 +57,7 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.horizontalGutters
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.ui.BackupsIconColors
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
import org.thoughtcrime.securesms.fonts.MonoTypeface
import org.thoughtcrime.securesms.registration.ui.shared.RegistrationScreen
@@ -78,6 +79,8 @@ fun EnterBackupKeyScreen(
onSkip: () -> Unit = {},
dialogContent: @Composable () -> Unit
) {
TemporaryScreenshotSecurity.bind()
val coroutineScope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
@@ -36,6 +36,7 @@ import org.signal.core.ui.compose.CircularProgressWrapper
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
import org.thoughtcrime.securesms.fonts.MonoTypeface
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.ui.restore.AccountEntropyPoolVerification
@@ -59,6 +60,8 @@ fun EnterLocalBackupKeyScreen(
onRegistrationErrorDismiss: () -> Unit = {},
onBackupKeyHelp: () -> Unit = {}
) {
TemporaryScreenshotSecurity.bind()
val visualTransform = remember { BackupKeyVisualTransformation(chunkSize = 4) }
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }