diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt
index 949f69eca..69d85aff3 100644
--- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt
+++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt
@@ -418,6 +418,9 @@ class ClipboardHistoryManager(
}
if (TextUtils.isEmpty(content) && imageUri == null) return
+ // passwords (marked as sensitive by the copying app) and one-time codes are not kept in the history
+ if (latinIME.mSettings.current.mClipboardSkipSensitive && imageUri == null
+ && isSensitiveForHistory(clipData.description, content)) return
clipboardDao?.addClip(timeStamp, false, content, imageUri)
}
}
@@ -584,6 +587,10 @@ class ClipboardHistoryManager(
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
}
+ /** Clip marked as sensitive (e.g. by password managers), or a one-time code (4 to 8 digits) */
+ private fun isSensitiveForHistory(description: android.content.ClipDescription?, content: String): Boolean =
+ ClipboardManagerCompat.getClipSensitivity(description) == true || isOneTimeCode(content)
+
private fun isClipSensitive(inputType: Int): Boolean {
ClipboardManagerCompat.getClipSensitivity(clipboardManager.primaryClip?.description)?.let { return it }
return InputTypeUtils.isPasswordInputType(inputType)
@@ -801,5 +808,8 @@ class ClipboardHistoryManager(
companion object {
const val RECENT_TIME_MILLIS = 1 * 60 * 1000L // 1 minute (for clipboard suggestions)
const val RECENT_SCREENSHOT_TIME_MILLIS = 5 * 60 * 1000L // 5 minutes
+ private val ONE_TIME_CODE = Regex("\\d{4,8}")
+ fun isOneTimeCode(text: String) = ONE_TIME_CODE.matches(text.trim())
}
+
}
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
index e21cd68ac..41ad8c325 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
+++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
@@ -215,6 +215,7 @@ object Defaults {
const val PREF_TOUCHPAD_FULLSCREEN = false
const val PREF_FORCE_AUTO_CAPS = false
const val PREF_SHIFT_RECAPITALIZES_WORD = false
+ const val PREF_CLIPBOARD_SKIP_SENSITIVE = false
const val PREF_OFFLINE_TEMP = 0.1f // Lower for faster, more deterministic proofreading
const val PREF_OFFLINE_TOP_P = 0.5f // Lower for faster token sampling
const val PREF_OFFLINE_TOP_K = 40
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt
index b28526dcb..21f9fd03b 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt
+++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt
@@ -484,6 +484,7 @@ class Settings private constructor() : SharedPreferences.OnSharedPreferenceChang
const val PREF_PERSIST_TEXT_EDIT_MODE = "persist_text_edit_mode"
const val PREF_FORCE_AUTO_CAPS = "force_auto_caps"
const val PREF_SHIFT_RECAPITALIZES_WORD = "shift_recapitalizes_word"
+ const val PREF_CLIPBOARD_SKIP_SENSITIVE = "clipboard_skip_sensitive"
const val PREF_OFFLINE_TEMP = "offline_temp"
const val PREF_OFFLINE_TOP_P = "offline_top_p"
const val PREF_OFFLINE_TOP_K = "offline_top_k"
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt
index decdbbdb0..e0f70cd08 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt
+++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt
@@ -105,6 +105,7 @@ open class SettingsValues(
val mTouchpadFullscreen: Boolean
val mForceAutoCaps: Boolean
val mShiftRecapitalizesWord: Boolean
+ val mClipboardSkipSensitive: Boolean
val mDeleteSwipeEnabled: Boolean
val mDeleteSwipeWordByWord: Boolean
val mAutospaceAfterPunctuation: Boolean
@@ -446,6 +447,7 @@ open class SettingsValues(
mTouchpadFullscreen = prefs.getBoolean(Settings.PREF_TOUCHPAD_FULLSCREEN, Defaults.PREF_TOUCHPAD_FULLSCREEN)
mForceAutoCaps = prefs.getBoolean(Settings.PREF_FORCE_AUTO_CAPS, Defaults.PREF_FORCE_AUTO_CAPS)
mShiftRecapitalizesWord = prefs.getBoolean(Settings.PREF_SHIFT_RECAPITALIZES_WORD, Defaults.PREF_SHIFT_RECAPITALIZES_WORD)
+ mClipboardSkipSensitive = prefs.getBoolean(Settings.PREF_CLIPBOARD_SKIP_SENSITIVE, Defaults.PREF_CLIPBOARD_SKIP_SENSITIVE)
mDeleteSwipeEnabled = prefs.getBoolean(Settings.PREF_DELETE_SWIPE, Defaults.PREF_DELETE_SWIPE)
mDeleteSwipeWordByWord = prefs.getBoolean(Settings.PREF_DELETE_SWIPE_WORD_BY_WORD, Defaults.PREF_DELETE_SWIPE_WORD_BY_WORD)
mAutospaceAfterPunctuation = prefs.getBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, Defaults.PREF_AUTOSPACE_AFTER_PUNCTUATION)
diff --git a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt
index 8844efe1d..d9eba99ce 100644
--- a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt
+++ b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt
@@ -621,7 +621,7 @@ private fun getCategoryForPrefKey(key: String): BackupCategory {
val clipboardKeys = setOf(
"enable_clipboard_history", "suggest_screenshots", "compress_screenshots",
"clipboard_history_retention_time", "clipboard_history_pinned_first",
- "clipboard_fold_pinned", "clear_clipboard_icon"
+ "clipboard_fold_pinned", "clear_clipboard_icon", "clipboard_skip_sensitive"
)
if (clipboardKeys.contains(key)) return BackupCategory.CLIPBOARD
diff --git a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt
index 574658057..06fe76ef2 100644
--- a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt
+++ b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt
@@ -99,6 +99,7 @@ fun PreferencesScreen(
R.string.settings_category_clipboard_history,
Settings.PREF_ENABLE_CLIPBOARD_HISTORY,
if (clipboardHistoryEnabled) Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME else null,
+ if (clipboardHistoryEnabled) Settings.PREF_CLIPBOARD_SKIP_SENSITIVE else null,
if (clipboardHistoryEnabled) Settings.PREF_CLIPBOARD_HISTORY_PINNED_FIRST else null,
if (clipboardHistoryEnabled) Settings.PREF_CLIPBOARD_FOLD_PINNED else null,
)
@@ -220,6 +221,10 @@ fun createPreferencesSettings(context: Context) = listOf(
val ctx = LocalContext.current
SwitchPreference(it, Defaults.PREF_ENABLE_CLIPBOARD_HISTORY) { ClipboardDao.getInstance(ctx)?.clearNonPinned() }
},
+ Setting(context, Settings.PREF_CLIPBOARD_SKIP_SENSITIVE,
+ R.string.clipboard_skip_sensitive, R.string.clipboard_skip_sensitive_summary) {
+ SwitchPreference(it, Defaults.PREF_CLIPBOARD_SKIP_SENSITIVE)
+ },
Setting(context, Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME, R.string.clipboard_history_retention_time) { setting ->
val ctx = LocalContext.current
SliderPreference(
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 3e17d1558..9650cccb0 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1738,4 +1738,6 @@ Ermöglicht lokale GGUF / llama.cpp-Inferenz für Korrekturlesen und Umschreiben
Benutzerdefinierte Zielsprache
Systemanweisung
Systemanweisung übersetzen
+ Passwörter und Codes nicht speichern
+ Als vertraulich markierte Einträge (z. B. aus Passwort-Managern) und Einmalcodes mit 4 bis 8 Ziffern landen nicht im Verlauf. Einfügen funktioniert weiterhin
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 599a043bd..316b5efce 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2422,4 +2422,6 @@ New dictionary:
Custom Target Language
System Instruction
Translate System Instruction
+ Don\'t keep passwords and codes
+ Entries marked as confidential (e.g. from password managers) and one-time codes with 4 to 8 digits are not added to the history. Pasting still works
diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
index 54ddc4118..e2ca4b5a7 100644
--- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
+++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
@@ -401,6 +401,14 @@ class InputLogicTest {
assertEquals(false, capsLockFromRecapitalize.getBoolean(inputLogic))
}
+ @Test fun `one-time codes are detected for the clipboard history`() {
+ assertEquals(true, ClipboardHistoryManager.isOneTimeCode("123456"))
+ assertEquals(true, ClipboardHistoryManager.isOneTimeCode(" 4821 "))
+ assertEquals(false, ClipboardHistoryManager.isOneTimeCode("123"))
+ assertEquals(false, ClipboardHistoryManager.isOneTimeCode("0611 123456"))
+ assertEquals(false, ClipboardHistoryManager.isOneTimeCode("abc123"))
+ }
+
@Test fun `shift at end of word does not change the word by default`() {
reset()
chainInput("hello")