Added support for disabling clipboard history and clip retention time

This commit is contained in:
pdroidandroid@gmail.com 2022-02-18 21:08:29 +01:00
parent 1bfb6e2273
commit ad1672ad5c
14 changed files with 124 additions and 29 deletions

View file

@ -67,7 +67,7 @@ class ClipboardAdapter(
}
fun setContent(historyEntry: ClipboardHistoryEntry?) {
itemView.tag = historyEntry?.id
itemView.tag = historyEntry?.timeStamp
contentView.text = historyEntry?.content
pinnedIconView.visibility = if (historyEntry?.isPinned == true) View.VISIBLE else View.GONE
}

View file

@ -136,6 +136,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
keyVisualAttr: KeyVisualAttributes?,
iconSet: KeyboardIconsSet
) {
historyManager.prepareClipboardHistory()
historyManager.setHistoryChangeListener(this)
clipboardHistoryManager = historyManager
clipboardAdapter.clipboardHistoryManager = historyManager

View file

@ -21,6 +21,7 @@ import android.util.Log;
import org.dslul.openboard.inputmethod.event.Event;
import org.dslul.openboard.inputmethod.latin.common.Constants;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
import org.dslul.openboard.inputmethod.latin.utils.CapsModeUtils;
import org.dslul.openboard.inputmethod.latin.utils.RecapitalizeStatus;
@ -689,7 +690,11 @@ public final class KeyboardState {
} else if (code == Constants.CODE_ALPHA_FROM_EMOJI) {
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
} else if (code == Constants.CODE_CLIPBOARD) {
setClipboardKeyboard();
// Note: Printing clipboard content is handled in
// {@link InputLogic#handleFunctionalEvent(Event,InputTransaction,int,LatinIME.UIHandler)}.
if (Settings.getInstance().getCurrent().mClipboardHistoryEnabled) {
setClipboardKeyboard();
}
} else if (code == Constants.CODE_ALPHA_FROM_CLIPBOARD) {
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
}

View file

@ -1,13 +1,13 @@
package org.dslul.openboard.inputmethod.latin
data class ClipboardHistoryEntry (
var id: Long,
var timeStamp: Long,
val content: CharSequence,
var isPinned: Boolean = false
) : Comparable<ClipboardHistoryEntry> {
override fun compareTo(other: ClipboardHistoryEntry): Int {
val result = other.isPinned.compareTo(isPinned)
return if (result != 0) result else other.id.compareTo(id)
return if (result != 0) result else other.timeStamp.compareTo(timeStamp)
}
}

View file

@ -2,11 +2,9 @@ package org.dslul.openboard.inputmethod.latin
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import androidx.annotation.RequiresApi
import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat
import org.dslul.openboard.inputmethod.latin.utils.JsonUtils
import java.io.File
@ -44,7 +42,12 @@ class ClipboardHistoryManager(
clipboardManager.removePrimaryClipChangedListener(this)
}
override fun onPrimaryClipChanged() = fetchPrimaryClip()
override fun onPrimaryClipChanged() {
// Make sure we read clipboard content only if history settings is set
if (latinIME.mSettings.current?.mClipboardHistoryEnabled == true) {
fetchPrimaryClip()
}
}
private fun fetchPrimaryClip() {
val clipData = clipboardManager.primaryClip ?: return
@ -53,14 +56,14 @@ class ClipboardHistoryManager(
// Starting from API 30, onPrimaryClipChanged() can be called multiple times
// for the same clip. We can identify clips with their timestamps since API 26.
// We use that to prevent unwanted duplicates.
val id = ClipboardManagerCompat.getClipTimestamp(clipData)?.also { stamp ->
if (historyEntries.any { it.id == stamp }) return
val timeStamp = ClipboardManagerCompat.getClipTimestamp(clipData)?.also { stamp ->
if (historyEntries.any { it.timeStamp == stamp }) return
} ?: System.currentTimeMillis()
val content = clipItem.coerceToText(latinIME)
if (TextUtils.isEmpty(content)) return
val entry = ClipboardHistoryEntry(id, content)
val entry = ClipboardHistoryEntry(timeStamp, content)
historyEntries.add(entry)
sortHistoryEntries()
val at = historyEntries.indexOf(entry)
@ -68,10 +71,10 @@ class ClipboardHistoryManager(
}
}
fun toggleClipPinned(clipId: Long) {
val from = historyEntries.indexOfFirst { it.id == clipId }
fun toggleClipPinned(ts: Long) {
val from = historyEntries.indexOfFirst { it.timeStamp == ts }
val historyEntry = historyEntries[from].apply {
id = System.currentTimeMillis()
timeStamp = System.currentTimeMillis()
isPinned = !isPinned
}
sortHistoryEntries()
@ -94,16 +97,34 @@ class ClipboardHistoryManager(
historyEntries.sort()
}
private fun checkClipRetentionElapsed() {
val mins = latinIME.mSettings.current.mClipboardHistoryRetentionTime
if (mins <= 0) return // No retention limit
val maxClipRetentionTime = mins * 60 * 1000L
val now = System.currentTimeMillis()
historyEntries.removeAll { !it.isPinned && (now - it.timeStamp) > maxClipRetentionTime }
}
// We do not want to update history while user is visualizing it, so we check retention only
// when history is about to be shown
fun prepareClipboardHistory() = checkClipRetentionElapsed()
fun getHistorySize() = historyEntries.size
fun getHistoryEntry(position: Int) = historyEntries[position]
fun getHistoryEntryContent(id: Long) = historyEntries.first { it.id == id }
fun getHistoryEntryContent(timeStamp: Long) = historyEntries.first { it.timeStamp == timeStamp }
fun setHistoryChangeListener(l: OnHistoryChangeListener?) {
onHistoryChangeListener = l
}
fun retrieveClipboardContent(): CharSequence {
val clipData = clipboardManager.primaryClip ?: return ""
if (clipData.itemCount == 0) return ""
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
}
private fun startLoadPinnedClipsFromDisk() {
object : Thread("$TAG-load") {
override fun run() {

View file

@ -693,8 +693,17 @@ public final class InputLogic {
// handled in {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_CLIPBOARD:
// Note: Switching clipboard keyboard is being handled in
// {@link KeyboardState#onEvent(Event,int)}.
// Note: If clipboard history is enabled, switching to clipboard keyboard
// is being handled in {@link KeyboardState#onEvent(Event,int)}.
// If disabled, current clipboard content is committed.
if (!inputTransaction.getMSettingsValues().mClipboardHistoryEnabled) {
final CharSequence content = mLatinIME.getClipboardHistoryManager()
.retrieveClipboardContent();
if (!TextUtils.isEmpty(content)) {
mConnection.commitText(content, 1);
inputTransaction.setDidAffectContents();
}
}
break;
case Constants.CODE_ALPHA_FROM_CLIPBOARD:
// Note: Switching back from clipboard keyboard to the main keyboard is being

View file

@ -60,9 +60,8 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
setupKeypressVibrationDurationSettings();
setupKeypressSoundVolumeSettings();
setupKeyLongpressTimeoutSettings();
refreshEnablingsOfKeypressSoundAndVibrationSettings();
refreshEnablingsOfKeypressSoundAndVibrationSettings();
setupHistoryRetentionTimeSettings();
refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings();
}
@Override
@ -79,16 +78,18 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
refreshEnablingsOfKeypressSoundAndVibrationSettings();
refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings();
}
private void refreshEnablingsOfKeypressSoundAndVibrationSettings() {
private void refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings() {
final SharedPreferences prefs = getSharedPreferences();
final Resources res = getResources();
setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
Settings.readVibrationEnabled(prefs, res));
setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME,
Settings.readKeypressSoundEnabled(prefs, res));
setPreferenceEnabled(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME,
Settings.readClipboardHistoryEnabled(prefs));
}
private void setupKeypressVibrationDurationSettings() {
@ -191,11 +192,11 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
});
}
private void setupKeyLongpressTimeoutSettings() {
private void setupHistoryRetentionTimeSettings() {
final SharedPreferences prefs = getSharedPreferences();
final Resources res = getResources();
final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
Settings.PREF_KEY_LONGPRESS_TIMEOUT);
Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME);
if (pref == null) {
return;
}
@ -212,17 +213,20 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
@Override
public int readValue(final String key) {
return Settings.readKeyLongpressTimeout(prefs, res);
return Settings.readClipboardHistoryRetentionTime(prefs, res);
}
@Override
public int readDefaultValue(final String key) {
return Settings.readDefaultKeyLongpressTimeout(res);
return Settings.readDefaultClipboardHistoryRetentionTime(res);
}
@Override
public String getValueText(final int value) {
return res.getString(R.string.abbreviation_unit_milliseconds, value);
if (value <= 0) {
return res.getString(R.string.settings_no_limit);
}
return res.getString(R.string.abbreviation_unit_minutes, value);
}
@Override

View file

@ -118,6 +118,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_SPACE_TO_CHANGE_LANG = "prefs_long_press_keyboard_to_change_lang";
public static final String PREF_ENABLE_CLIPBOARD_HISTORY = "pref_enable_clipboard_history";
public static final String PREF_CLIPBOARD_HISTORY_RETENTION_TIME = "pref_clipboard_history_retention_time";
// This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
// This is being used only for the backward compatibility.
private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
@ -348,6 +351,22 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
final int milliseconds = prefs.getInt(prefKey, UNDEFINED_PREFERENCE_VALUE_INT);
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue;
}
public static boolean readClipboardHistoryEnabled(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_ENABLE_CLIPBOARD_HISTORY, true);
}
public static int readClipboardHistoryRetentionTime(final SharedPreferences prefs,
final Resources res) {
final int minutes = prefs.getInt(
PREF_CLIPBOARD_HISTORY_RETENTION_TIME, UNDEFINED_PREFERENCE_VALUE_INT);
return (minutes != UNDEFINED_PREFERENCE_VALUE_INT) ? minutes
: readDefaultClipboardHistoryRetentionTime(res);
}
public static int readDefaultClipboardHistoryRetentionTime(final Resources res) {
return res.getInteger(R.integer.config_clipboard_history_retention_time);
}
public static boolean readShowsNumberRow(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_SHOW_NUMBER_ROW, false);

View file

@ -80,6 +80,8 @@ public class SettingsValues {
public final boolean mBlockPotentiallyOffensive;
public final boolean mSpaceTrackpadEnabled;
public final boolean mDeleteSwipeEnabled;
public final boolean mClipboardHistoryEnabled;
public final long mClipboardHistoryRetentionTime;
// Use bigrams to predict the next word when there is no input for it yet
public final boolean mBigramPredictionEnabled;
public final boolean mGestureInputEnabled;
@ -233,6 +235,8 @@ public class SettingsValues {
}
mSpaceTrackpadEnabled = Settings.readSpaceTrackpadEnabled(prefs);
mDeleteSwipeEnabled = Settings.readDeleteSwipeEnabled(prefs);
mClipboardHistoryEnabled = Settings.readClipboardHistoryEnabled(prefs);
mClipboardHistoryRetentionTime = Settings.readClipboardHistoryRetentionTime(prefs, res);
}
public boolean isMetricsLoggingEnabled() {

View file

@ -443,8 +443,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
return;
}
if (view == mClipboardKey) {
final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.CLIPBOARD);
mListener.onCodeInput(Constants.CODE_CLIPBOARD,
Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
false /* isKeyRepeat */);
return;
}

View file

@ -142,7 +142,7 @@ public final class JsonUtils {
writer.beginArray();
for (final ClipboardHistoryEntry e : entries) {
writer.beginObject();
writer.name(CLIPBOARD_HISTORY_ENTRY_ID_KEY).value(e.getId());
writer.name(CLIPBOARD_HISTORY_ENTRY_ID_KEY).value(e.getTimeStamp());
writer.name(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY).value(e.getContent().toString());
writer.endObject();
}

View file

@ -22,6 +22,9 @@
<resources>
<bool name="config_use_fullscreen_mode">false</bool>
<!-- Maximum time for a clipboard history entry to be kept, in minutes. -->
<integer name="config_clipboard_history_retention_time">10</integer>
<dimen name="config_key_hysteresis_distance">8.0dp</dimen>
<!-- Preferable keyboard height in absolute scale: 1.285in -->

View file

@ -57,6 +57,8 @@
<string name="settings_category_input">Input</string>
<!-- Settings category title for Preference/Additional keys settings [CHAR LIMIT=33] -->
<string name="settings_category_additional_keys">Additional keys</string>
<!-- Settings category title for Preference/Additional keys settings [CHAR LIMIT=33] -->
<string name="settings_category_clipboard_history">Clipboard history</string>
<!-- Settings category title for Text correction/Corrections [CHAR LIMIT=33] -->
<string name="settings_category_correction">Corrections</string>
<!-- Settings category title for Text correction/Suggestions [CHAR LIMIT=33] -->
@ -97,8 +99,12 @@
<string name="key_preview_popup_dismiss_default_delay">Default</string>
<!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
<string name="abbreviation_unit_milliseconds"><xliff:g id="MILLISECONDS">%s</xliff:g>ms</string>
<!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
<string name="abbreviation_unit_minutes"><xliff:g id="MILLISECONDS">%s</xliff:g>min.</string>
<!-- The text that represents the current settings value is the system default [CHAR LIMIT=25] -->
<string name="settings_system_default">System default</string>
<!-- The text that represents an unlimited delay [CHAR LIMIT=25] -->
<string name="settings_no_limit">No limit</string>
<!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
<string name="use_contacts_dict">Suggest Contact names</string>
<!-- Description for option enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=65] -->
@ -166,6 +172,12 @@
<string name="voice_input_disabled_summary">No voice input methods enabled. Check Languages &amp; input settings.</string>
<!-- Preferences item for enabling clipboard key -->
<string name="show_clipboard_key">Clipboard key</string>
<!-- Preferences item for enabling clipboard history -->
<string name="enable_clipboard_history">Enable clipboard history</string>
<!-- Description for enabling/disabling clipboard history mentioning that if disabled, clipboard content is pasted -->
<string name="enable_clipboard_history_summary">If disabled, clipboard key will paste clipboard content if any</string>
<!-- Preferences item for enabling clipboard history -->
<string name="clipboard_history_retention_time">History retention time</string>
<!-- Preferences item for enabling swipe deletion -->
<string name="delete_swipe">Delete swipe</string>
<!-- Description for "delete_swipe" option. -->

View file

@ -93,4 +93,20 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_category_clipboard_history">
<CheckBoxPreference
android:key="pref_enable_clipboard_history"
android:title="@string/enable_clipboard_history"
android:summary="@string/enable_clipboard_history_summary"
android:defaultValue="true"
android:persistent="true" />
<org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference
android:key="pref_clipboard_history_retention_time"
android:title="@string/clipboard_history_retention_time"
latin:maxValue="120" /> <!-- minutes -->
</PreferenceCategory>
</PreferenceScreen>