diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt index 41ae0dfa..bb282744 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardAdapter.kt @@ -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 } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt index 819942dc..5b46fe3b 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/clipboard/ClipboardHistoryView.kt @@ -136,6 +136,7 @@ class ClipboardHistoryView @JvmOverloads constructor( keyVisualAttr: KeyVisualAttributes?, iconSet: KeyboardIconsSet ) { + historyManager.prepareClipboardHistory() historyManager.setHistoryChangeListener(this) clipboardHistoryManager = historyManager clipboardAdapter.clipboardHistoryManager = historyManager diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java index 4b5177a2..098de98f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java @@ -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); } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt index 5f071907..03a1839a 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryEntry.kt @@ -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 { 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) } } \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt index b932bcd0..fbf3b7a9 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt @@ -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() { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java index 31bcc6bf..1f85f662 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java @@ -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 diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/PreferencesSettingsFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/PreferencesSettingsFragment.java index 4cfb0c9b..a82b885f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/PreferencesSettingsFragment.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/PreferencesSettingsFragment.java @@ -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 diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java index fbdf2ea6..7400620e 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java @@ -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); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java index 420273c0..76c536ca 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java @@ -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() { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java index 875dbdd3..baf690d7 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java @@ -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; } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java index 6851794a..34247dd6 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JsonUtils.java @@ -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(); } diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml index 1e8a6959..240dc243 100644 --- a/app/src/main/res/values/config.xml +++ b/app/src/main/res/values/config.xml @@ -22,6 +22,9 @@ false + + 10 + 8.0dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 667a2ee8..c92b0fc4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -57,6 +57,8 @@ Input Additional keys + + Clipboard history Corrections @@ -97,8 +99,12 @@ Default %sms + + %smin. System default + + No limit Suggest Contact names @@ -166,6 +172,12 @@ No voice input methods enabled. Check LanguagesĀ & input settings. Clipboard key + + Enable clipboard history + + If disabled, clipboard key will paste clipboard content if any + + History retention time Delete swipe diff --git a/app/src/main/res/xml/prefs_screen_preferences.xml b/app/src/main/res/xml/prefs_screen_preferences.xml index 4df587f0..5f554af3 100644 --- a/app/src/main/res/xml/prefs_screen_preferences.xml +++ b/app/src/main/res/xml/prefs_screen_preferences.xml @@ -93,4 +93,20 @@ + + + + + + + +