Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto clear primary clipboard #688

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 @@ -174,7 +174,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
) {
initialize()
setupToolbarKeys()
historyManager.prepareClipboardHistory()
historyManager.cancelRetentionCheck()
historyManager.setHistoryChangeListener(this)
clipboardHistoryManager = historyManager
clipboardAdapter.clipboardHistoryManager = historyManager
Expand Down Expand Up @@ -202,6 +202,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
fun stopClipboardHistory() {
if (!initialized) return
clipboardRecyclerView.adapter = null
clipboardHistoryManager?.scheduleRetentionCheck()
clipboardHistoryManager?.setHistoryChangeListener(null)
clipboardHistoryManager = null
clipboardAdapter.clipboardHistoryManager = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,45 @@ package helium314.keyboard.latin

import android.content.ClipboardManager
import android.content.Context
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import helium314.keyboard.compat.ClipboardManagerCompat
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.DeviceProtectedUtils
import kotlin.collections.ArrayList

class ClipboardHistoryManager(
private val latinIME: LatinIME
) : ClipboardManager.OnPrimaryClipChangedListener {
) : ClipboardManager.OnPrimaryClipChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {

private lateinit var clipboardManager: ClipboardManager
private var onHistoryChangeListener: OnHistoryChangeListener? = null
private var isRetentionCheckScheduled = false
private var clipboardHistoryEnabled = true
private var maxClipRetentionTime = DEFAULT_RETENTION_TIME_MIN * ONE_MINUTE_MILLIS
private val retentionCheckHandler = Handler(Looper.getMainLooper())
private val retentionCheckRunnable: Runnable = object : Runnable {
override fun run() {
checkClipRetentionElapsed()
retentionCheckHandler.postDelayed(this, maxClipRetentionTime)
}
}

fun onCreate() {
clipboardManager = latinIME.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardHistoryEnabled = latinIME.mSettings.current.mClipboardHistoryEnabled
maxClipRetentionTime = latinIME.mSettings.current.mClipboardHistoryRetentionTime * ONE_MINUTE_MILLIS
clipboardManager.addPrimaryClipChangedListener(this)
if (historyEntries.isEmpty())
loadPinnedClips()
if (latinIME.mSettings.current?.mClipboardHistoryEnabled == true)
fetchPrimaryClip()
scheduleRetentionCheck()
DeviceProtectedUtils.getSharedPreferences(latinIME).registerOnSharedPreferenceChangeListener(this)
}

fun onPinnedClipsAvailable(pinnedClips: List<ClipboardHistoryEntry>) {
Expand All @@ -39,15 +57,41 @@ class ClipboardHistoryManager(

fun onDestroy() {
clipboardManager.removePrimaryClipChangedListener(this)
cancelRetentionCheck()
}

override fun onPrimaryClipChanged() {
// Make sure we read clipboard content only if history settings is set
if (latinIME.mSettings.current?.mClipboardHistoryEnabled == true) {
if (clipboardHistoryEnabled) {
fetchPrimaryClip()
}
}

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
Settings.PREF_ENABLE_CLIPBOARD_HISTORY -> {
clipboardHistoryEnabled = Settings.readClipboardHistoryEnabled(sharedPreferences)
if (clipboardHistoryEnabled) {
scheduleRetentionCheck()
} else {
cancelRetentionCheck()
clearHistory()
}
}
Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME -> {
val newRetentionTimeMinutes =
sharedPreferences?.getInt(key, DEFAULT_RETENTION_TIME_MIN) ?: return
if (newRetentionTimeMinutes == 0) {
cancelRetentionCheck()
}
else if (maxClipRetentionTime == 0L) { // retention limit has been enabled
scheduleRetentionCheck(newRetentionTimeMinutes * ONE_MINUTE_MILLIS)
}
maxClipRetentionTime = newRetentionTimeMinutes * ONE_MINUTE_MILLIS
}
}
}

private fun fetchPrimaryClip() {
val clipData = clipboardManager.primaryClip ?: return
if (clipData.itemCount == 0 || clipData.description?.hasMimeType("text/*") == false) return
Expand Down Expand Up @@ -113,16 +157,24 @@ class ClipboardHistoryManager(
}

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()
if (latinIME.mSettings.current?.mClearPrimaryClipboard == true) {
ClipboardManagerCompat.clearPrimaryClip(clipboardManager)
}
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 scheduleRetentionCheck(timeMillis: Long = maxClipRetentionTime) {
if (isRetentionCheckScheduled || !clipboardHistoryEnabled || timeMillis <= 0) return
retentionCheckHandler.postDelayed(retentionCheckRunnable, timeMillis)
isRetentionCheckScheduled = true
}

fun cancelRetentionCheck() {
if (!isRetentionCheckScheduled) return
retentionCheckHandler.removeCallbacksAndMessages(null)
isRetentionCheckScheduled = false
}

fun getHistorySize() = historyEntries.size

Expand Down Expand Up @@ -162,5 +214,7 @@ class ClipboardHistoryManager(
companion object {
// store pinned clips in companion object so they survive a keyboard switch (which destroys the current instance)
private val historyEntries: MutableList<ClipboardHistoryEntry> = ArrayList()
private const val ONE_MINUTE_MILLIS = 60 * 1000L
private const val DEFAULT_RETENTION_TIME_MIN = 10
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/helium314/keyboard/latin/LatinIME.java
Original file line number Diff line number Diff line change
Expand Up @@ -584,9 +584,9 @@ public void onCreate() {
KeyboardSwitcher.init(this);
super.onCreate();

mClipboardHistoryManager.onCreate();
mHandler.onCreate();
loadSettings();
mClipboardHistoryManager.onCreate();

// Register to receive ringer mode change.
final IntentFilter filter = new IntentFilter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ private void refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings
Settings.readKeypressSoundEnabled(prefs, res));
setPreferenceVisible(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME,
Settings.readClipboardHistoryEnabled(prefs));
setPreferenceVisible(Settings.PREF_CLEAR_PRIMARY_CLIPBOARD,
Settings.readClipboardHistoryEnabled(prefs)
&& Settings.readClipboardHistoryRetentionTime(prefs, res) > 0);
}

private void setupKeypressVibrationDurationSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang

public static final String PREF_ENABLE_CLIPBOARD_HISTORY = "enable_clipboard_history";
public static final String PREF_CLIPBOARD_HISTORY_RETENTION_TIME = "clipboard_history_retention_time";
public static final String PREF_CLEAR_PRIMARY_CLIPBOARD = "clear_primary_clipboard";

public static final String PREF_SECONDARY_LOCALES_PREFIX = "secondary_locales_";
public static final String PREF_ADD_TO_PERSONAL_DICTIONARY = "add_to_personal_dictionary";
Expand Down Expand Up @@ -387,6 +388,10 @@ public static int readDefaultClipboardHistoryRetentionTime(final Resources res)
return res.getInteger(R.integer.config_clipboard_history_retention_time);
}

public static boolean readClearPrimaryClipboard(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_CLEAR_PRIMARY_CLIPBOARD, false);
}

public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) {
return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, "none")) {
case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public class SettingsValues {
public final boolean mAutospaceAfterPunctuationEnabled;
public final boolean mClipboardHistoryEnabled;
public final long mClipboardHistoryRetentionTime;
public final boolean mClearPrimaryClipboard;
public final boolean mOneHandedModeEnabled;
public final int mOneHandedModeGravity;
public final float mOneHandedModeScale;
Expand Down Expand Up @@ -200,7 +201,7 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
mAutospaceAfterPunctuationEnabled = Settings.readAutospaceAfterPunctuationEnabled(prefs);
mClipboardHistoryEnabled = Settings.readClipboardHistoryEnabled(prefs);
mClipboardHistoryRetentionTime = Settings.readClipboardHistoryRetentionTime(prefs, res);

mClearPrimaryClipboard = Settings.readClearPrimaryClipboard(prefs);
mOneHandedModeEnabled = Settings.readOneHandedModeEnabled(prefs, mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
mOneHandedModeGravity = Settings.readOneHandedModeGravity(prefs, mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
if (mOneHandedModeEnabled) {
Expand Down
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 @@ -148,6 +148,8 @@
<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 clearing primary clipboard -->
<string name="clear_primary_clipboard">Clear primary clipboard</string>
<!-- Preferences item for enabling swipe deletion -->
<string name="delete_swipe">Delete swipe</string>
<!-- Description for "delete_swipe" option. -->
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/xml/prefs_screen_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@
android:title="@string/clipboard_history_retention_time"
latin:maxValue="120" /> <!-- minutes -->

<SwitchPreference
android:key="clear_primary_clipboard"
android:title="@string/clear_primary_clipboard"
android:defaultValue="false"
android:persistent="true" />

</PreferenceCategory>

</PreferenceScreen>