From 99191157644d83174f92abebd1a2ac8b1c0985d8 Mon Sep 17 00:00:00 2001 From: 4di-hello <4di-hello@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:58:01 +0200 Subject: [PATCH] Add option to not keep passwords and one-time codes in clipboard history Skips clips marked as sensitive by the copying app (ClipDescription EXTRA_IS_SENSITIVE, e.g. password managers) and one-time codes (4 to 8 digits). Off by default. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VuiaMhLXwhErf9shhrr1uD --- .../keyboard/latin/ClipboardHistoryManager.kt | 10 ++++++++++ .../java/helium314/keyboard/latin/settings/Defaults.kt | 1 + .../java/helium314/keyboard/latin/settings/Settings.kt | 1 + .../keyboard/latin/settings/SettingsValues.kt | 2 ++ .../settings/preferences/BackupRestorePreference.kt | 2 +- .../keyboard/settings/screens/PreferencesScreen.kt | 5 +++++ app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ .../java/helium314/keyboard/latin/InputLogicTest.kt | 8 ++++++++ 9 files changed, 32 insertions(+), 1 deletion(-) 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")