Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1738,4 +1738,6 @@ Ermöglicht lokale GGUF / llama.cpp-Inferenz für Korrekturlesen und Umschreiben
<string name="translation_custom_target_language_title">Benutzerdefinierte Zielsprache</string>
<string name="offline_system_instruction_title">Systemanweisung</string>
<string name="offline_translate_system_instruction_title">Systemanweisung übersetzen</string>
<string name="clipboard_skip_sensitive">Passwörter und Codes nicht speichern</string>
<string name="clipboard_skip_sensitive_summary">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</string>
</resources>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2422,4 +2422,6 @@ New dictionary:
<string name="translation_custom_target_language_title" tools:ignore="MissingTranslation">Custom Target Language</string>
<string name="offline_system_instruction_title" tools:ignore="MissingTranslation">System Instruction</string>
<string name="offline_translate_system_instruction_title" tools:ignore="MissingTranslation">Translate System Instruction</string>
<string name="clipboard_skip_sensitive">Don\'t keep passwords and codes</string>
<string name="clipboard_skip_sensitive_summary">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</string>
</resources>
8 changes: 8 additions & 0 deletions app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading