diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt index 28e9ff85..276a90db 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt @@ -3,13 +3,12 @@ package com.greenart7c3.nostrsigner.models import android.widget.Toast import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.Clipboard import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.DataStoreAccess import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.service.nip44v3.Nip44v3 -import com.greenart7c3.nostrsigner.ui.newSensitivePlainText +import com.greenart7c3.nostrsigner.ui.setSensitiveClip import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray @@ -120,11 +119,7 @@ class Account( didBackup = true val nsec = getNsec() Amber.instance.applicationIOScope.launch(Dispatchers.Main) { - clipboardManager.setClipEntry( - ClipEntry( - newSensitivePlainText("", nsec), - ), - ) + clipboardManager.setSensitiveClip("", nsec) Toast.makeText( Amber.instance, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SensitiveClipboard.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SensitiveClipboard.kt index a25a31da..fef0e4c6 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SensitiveClipboard.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SensitiveClipboard.kt @@ -4,6 +4,15 @@ import android.content.ClipData import android.content.ClipDescription import android.os.Build import android.os.PersistableBundle +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.Clipboard +import com.greenart7c3.nostrsigner.Amber +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** How long a copied secret stays on the clipboard before it is cleared. */ +const val SENSITIVE_CLIPBOARD_CLEAR_DELAY_MS = 60_000L /** * Creates a [ClipData] flagged as sensitive so the system (Android 13+) avoids @@ -22,3 +31,43 @@ fun newSensitivePlainText(label: CharSequence, text: CharSequence): ClipData { clipData.description.extras = extras return clipData } + +/** + * Copies a secret to the clipboard flagged as sensitive content and schedules it + * to be cleared after [clearAfterMillis]. The clipboard is only cleared if it + * still contains the copied secret, so anything the user copies afterwards is + * left untouched. + */ +suspend fun Clipboard.setSensitiveClip( + label: CharSequence, + text: CharSequence, + scope: CoroutineScope = Amber.instance.applicationIOScope, + clearAfterMillis: Long = SENSITIVE_CLIPBOARD_CLEAR_DELAY_MS, +) { + setClipEntry(ClipEntry(newSensitivePlainText(label, text))) + scheduleSensitiveClear(text, scope, clearAfterMillis) +} + +private fun Clipboard.scheduleSensitiveClear( + copiedValue: CharSequence, + scope: CoroutineScope, + delayMillis: Long, +) { + scope.launch { + delay(delayMillis) + val currentText = getClipEntry()?.clipData?.let { clip -> + if (clip.itemCount > 0) clip.getItemAt(0).text?.toString() else null + } + if (currentText == copiedValue.toString()) { + clearClipboard() + } + } +} + +private suspend fun Clipboard.clearClipboard() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + nativeClipboard.clearPrimaryClip() + } else { + setClipEntry(ClipEntry(ClipData.newPlainText("", ""))) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupScreen.kt index d884f232..18264f60 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupScreen.kt @@ -69,7 +69,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.autofill.ContentType import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.Clipboard import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext @@ -106,7 +105,7 @@ import com.greenart7c3.nostrsigner.ui.components.IconRow import com.greenart7c3.nostrsigner.ui.components.MarkdownText import com.greenart7c3.nostrsigner.ui.components.SeedWordsPage import com.greenart7c3.nostrsigner.ui.navigation.Route -import com.greenart7c3.nostrsigner.ui.newSensitivePlainText +import com.greenart7c3.nostrsigner.ui.setSensitiveClip import com.greenart7c3.nostrsigner.ui.theme.Size35dp import com.greenart7c3.nostrsigner.ui.theme.fromHex import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06 @@ -797,7 +796,7 @@ private fun encryptCopyNSec( account.didBackup = true onBackupDone() Amber.instance.applicationIOScope.launch(Dispatchers.Main) { - clipboardManager.setClipEntry(ClipEntry(newSensitivePlainText("", key))) + clipboardManager.setSensitiveClip("", key) Toast.makeText(context, context.getString(R.string.secret_key_copied_to_clipboard), Toast.LENGTH_SHORT).show() } onLoading(false) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt index dbb84703..8a4a8e98 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt @@ -19,7 +19,6 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -31,7 +30,7 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.ui.newSensitivePlainText +import com.greenart7c3.nostrsigner.ui.setSensitiveClip import com.greenart7c3.nostrsigner.ui.verticalScrollbar import kotlinx.coroutines.launch @@ -74,11 +73,7 @@ fun SeedWordsPage( AmberButton( onClick = { scope.launch { - clipboardManager.setClipEntry( - ClipEntry( - newSensitivePlainText("", seedWords.joinToString(" ")), - ), - ) + clipboardManager.setSensitiveClip("", seedWords.joinToString(" ")) } }, text = stringResource(R.string.copy_to_clipboard),