Mark the recovery key as sensitive when copying it to the clipboard

The recovery key was copied without EXTRA_IS_SENSITIVE, so Android 13+
showed it in the clipboard preview and keyboards could store it in
their clipboard history.

Add an isSensitive option to CopyToClipboardUseCase and
Context.copyToClipboard, and set it when copying the recovery key.
This commit is contained in:
Bardi Harborow
2026-10-04 20:24:22 +11:00
parent a6d9d27c68
commit f59f5da64c
3 changed files with 27 additions and 5 deletions
@@ -122,6 +122,7 @@ private fun Content(
context.copyToClipboard(
text = formattedRecoveryKey,
toastMessage = toastMessage,
isSensitive = true,
)
state.eventSink.invoke(SecureBackupSetupEvent.RecoveryKeyHasBeenSaved)
}
@@ -9,15 +9,35 @@
package io.element.android.libraries.androidutils.system
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.PersistableBundle
import androidx.core.content.getSystemService
class CopyToClipboardUseCase(
private val context: Context,
) {
fun execute(text: CharSequence) {
context.getSystemService<ClipboardManager>()
?.setPrimaryClip(ClipData.newPlainText("", text))
/**
* @param isSensitive if true, the clip is flagged so the system clipboard preview (Android 13+) and keyboards hide its content.
*/
fun execute(text: CharSequence, isSensitive: Boolean = false) {
val clipData = ClipData.newPlainText("", text)
if (isSensitive) {
clipData.description.extras = PersistableBundle().apply {
putBoolean(EXTRA_IS_SENSITIVE, true)
}
}
context.getSystemService<ClipboardManager>()?.setPrimaryClip(clipData)
}
private companion object {
// The constant only exists from API 33, but the documentation recommends using its value on older versions too.
val EXTRA_IS_SENSITIVE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ClipDescription.EXTRA_IS_SENSITIVE
} else {
"android.content.extra.IS_SENSITIVE"
}
}
}
@@ -63,9 +63,10 @@ fun Context.getVersionCodeFromManifest(): Long {
*/
fun Context.copyToClipboard(
text: CharSequence,
toastMessage: String? = null
toastMessage: String? = null,
isSensitive: Boolean = false,
) {
CopyToClipboardUseCase(this).execute(text)
CopyToClipboardUseCase(this).execute(text, isSensitive)
toastMessage?.let { toast(it) }
}