From 2dc9b12b136f583daee01f86c399957d517c484d Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 4 Nov 2023 13:38:24 +0100 Subject: [PATCH] store pinned clips in shared prferences instead of separate file --- .../latin/ClipboardHistoryEntry.kt | 20 ++++- .../latin/ClipboardHistoryManager.kt | 87 ++++--------------- .../inputmethod/latin/settings/Settings.java | 22 +++++ .../inputmethod/latin/utils/JsonUtils.java | 64 -------------- 4 files changed, 58 insertions(+), 135 deletions(-) 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 ef83eff46..a613af18b 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 @@ -2,14 +2,32 @@ package org.dslul.openboard.inputmethod.latin +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable data class ClipboardHistoryEntry ( var timeStamp: Long, + @Serializable(with = CharSequenceStringSerializer::class) 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.timeStamp.compareTo(timeStamp) } +} + +class CharSequenceStringSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("CharSequence", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: CharSequence) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder) = decoder.decodeString() } \ 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 d982787b2..163f3cd53 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 @@ -5,29 +5,26 @@ package org.dslul.openboard.inputmethod.latin import android.content.ClipboardManager import android.content.Context import android.text.TextUtils -import android.util.Base64 -import android.util.Log +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat -import org.dslul.openboard.inputmethod.latin.utils.JsonUtils -import java.io.File -import java.lang.Exception -import java.util.* +import org.dslul.openboard.inputmethod.latin.settings.Settings +import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils +import kotlin.collections.ArrayList class ClipboardHistoryManager( private val latinIME: LatinIME ) : ClipboardManager.OnPrimaryClipChangedListener { - private lateinit var pinnedHistoryClipsFile: File private lateinit var clipboardManager: ClipboardManager - private val historyEntries: MutableList + private val historyEntries: MutableList = ArrayList() private var onHistoryChangeListener: OnHistoryChangeListener? = null fun onCreate() { - pinnedHistoryClipsFile = File(latinIME.filesDir, PINNED_CLIPS_DATA_FILE_NAME) clipboardManager = latinIME.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager fetchPrimaryClip() clipboardManager.addPrimaryClipChangedListener(this) - startLoadPinnedClipsFromDisk() + loadPinnedClips() } fun onPinnedClipsAvailable(pinnedClips: List) { @@ -82,7 +79,7 @@ class ClipboardHistoryManager( sortHistoryEntries() val to = historyEntries.indexOf(historyEntry) onHistoryChangeListener?.onClipboardHistoryEntryMoved(from, to) - startSavePinnedClipsToDisk() + savePinnedClips() } fun clearHistory() { @@ -127,57 +124,16 @@ class ClipboardHistoryManager( return clipData.getItemAt(0)?.coerceToText(latinIME) ?: "" } - private fun startLoadPinnedClipsFromDisk() { - object : Thread("$TAG-load") { - override fun run() { - loadFromDisk() - } - }.start() + private fun loadPinnedClips() { + val pinnedClipString = Settings.readPinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME)) + if (pinnedClipString.isEmpty()) return + val pinnedClips: List = Json.decodeFromString(pinnedClipString) + latinIME.mHandler.postUpdateClipboardPinnedClips(pinnedClips) } - private fun loadFromDisk() { - // Debugging - if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canRead()) { - Log.w(TAG, "Attempt to read pinned clips file $pinnedHistoryClipsFile without permission") - } - var list = emptyList() - try { - if (pinnedHistoryClipsFile.exists()) { - val bytes = Base64.decode(pinnedHistoryClipsFile.readText(), Base64.DEFAULT) - list = JsonUtils.jsonBytesToHistoryEntryList(bytes) - } - } catch (e: Exception) { - Log.w(TAG, "Couldn't retrieve $pinnedHistoryClipsFile content", e) - } - latinIME.mHandler.postUpdateClipboardPinnedClips(list) - } - - private fun startSavePinnedClipsToDisk() { - val localCopy = historyEntries.filter { it.isPinned }.map { it.copy() } - object : Thread("$TAG-save") { - override fun run() { - saveToDisk(localCopy) - } - }.start() - } - - private fun saveToDisk(list: List) { - // Debugging - if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canWrite()) { - Log.w(TAG, "Attempt to write pinned clips file $pinnedHistoryClipsFile without permission") - } - try { - pinnedHistoryClipsFile.createNewFile() - val jsonStr = JsonUtils.historyEntryListToJsonStr(list) - if (!TextUtils.isEmpty(jsonStr)) { - val rawText = Base64.encodeToString(jsonStr.encodeToByteArray(), Base64.DEFAULT) - pinnedHistoryClipsFile.writeText(rawText) - } else { - pinnedHistoryClipsFile.writeText("") - } - } catch (e: Exception) { - Log.w(TAG, "Couldn't write to $pinnedHistoryClipsFile", e) - } + private fun savePinnedClips() { + val pinnedClips = Json.encodeToString(historyEntries.filter { it.isPinned }) + Settings.writePinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME), pinnedClips) } interface OnHistoryChangeListener { @@ -185,13 +141,4 @@ class ClipboardHistoryManager( fun onClipboardHistoryEntriesRemoved(pos: Int, count: Int) fun onClipboardHistoryEntryMoved(from: Int, to: Int) } - - companion object { - const val PINNED_CLIPS_DATA_FILE_NAME = "pinned_clips.data" - const val TAG = "ClipboardHistoryManager" - } - - init { - historyEntries = LinkedList() - } -} \ No newline at end of file +} 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 0d4e52099..2c2dc945f 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 @@ -39,6 +39,7 @@ import org.dslul.openboard.inputmethod.latin.utils.StatsUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -133,6 +134,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID = "last_shown_emoji_category_page_id"; + public static final String PREF_PINNED_CLIPS = "pinned_clips"; // used as a workaround against keyboard not showing edited theme in ColorsSettingsFragment public static final String PREF_FORCE_OPPOSITE_THEME = "force_opposite_theme"; @@ -147,6 +149,16 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang private static final Settings sInstance = new Settings(); + // preferences that are not used in SettingsValues + private static final HashSet dontReloadOnChanged = new HashSet<>() {{ + add(PREF_FORCE_OPPOSITE_THEME); + add(PREF_PINNED_CLIPS); + add(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID); + add(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID); + add(PREF_EMOJI_RECENT_KEYS); + add(PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG); + }}; + public static Settings getInstance() { return sInstance; } @@ -172,6 +184,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { + if (dontReloadOnChanged.contains(key)) + return; mSettingsValuesLock.lock(); try { if (mSettingsValues == null) { @@ -425,6 +439,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue); } + public static String readPinnedClipString(final SharedPreferences prefs) { + return prefs.getString(PREF_PINNED_CLIPS, ""); + } + + public static void writePinnedClipString(final SharedPreferences prefs, final String clips) { + prefs.edit().putString(PREF_PINNED_CLIPS, clips).apply(); + } + public static List readPinnedKeys(final SharedPreferences prefs) { final String pinnedKeysString = prefs.getString(Settings.PREF_PINNED_KEYS, ""); if (pinnedKeysString.isEmpty()) 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 20336525c..bcc8e9ada 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 @@ -6,20 +6,15 @@ package org.dslul.openboard.inputmethod.latin.utils; -import android.text.TextUtils; import android.util.JsonReader; import android.util.JsonWriter; import android.util.Log; -import org.dslul.openboard.inputmethod.latin.ClipboardHistoryEntry; -import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; -import java.io.InputStreamReader; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -28,8 +23,6 @@ public final class JsonUtils { private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName(); private static final String STRING_CLASS_NAME = String.class.getSimpleName(); - private static final String CLIPBOARD_HISTORY_ENTRY_ID_KEY = "id"; - private static final String CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY = "content"; private static final String EMPTY_STRING = ""; @@ -88,63 +81,6 @@ public final class JsonUtils { return EMPTY_STRING; } - public static List jsonBytesToHistoryEntryList(final byte[] bytes) { - final ArrayList list = new ArrayList<>(); - final JsonReader reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(bytes))); - try { - reader.beginArray(); - while (reader.hasNext()) { - reader.beginObject(); - long id = 0; - String content = EMPTY_STRING; - while (reader.hasNext()) { - final String name = reader.nextName(); - if (name.equals(CLIPBOARD_HISTORY_ENTRY_ID_KEY)) { - id = reader.nextLong(); - } else if (name.equals(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY)) { - content = reader.nextString(); - } else { - Log.w(TAG, "Invalid name: " + name); - reader.skipValue(); - } - } - if (id > 0 && !TextUtils.isEmpty(content)) { - list.add(new ClipboardHistoryEntry(id, content, true)); - } - reader.endObject(); - } - reader.endArray(); - return list; - } catch (final IOException e) { - } finally { - close(reader); - } - return Collections.emptyList(); - } - - public static String historyEntryListToJsonStr(final Collection entries) { - if (entries == null || entries.isEmpty()) { - return EMPTY_STRING; - } - final StringWriter sw = new StringWriter(); - final JsonWriter writer = new JsonWriter(sw); - try { - writer.beginArray(); - for (final ClipboardHistoryEntry e : entries) { - writer.beginObject(); - writer.name(CLIPBOARD_HISTORY_ENTRY_ID_KEY).value(e.getTimeStamp()); - writer.name(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY).value(e.getContent().toString()); - writer.endObject(); - } - writer.endArray(); - return sw.toString(); - } catch (final IOException e) { - } finally { - close(writer); - } - return EMPTY_STRING; - } - private static void close(final Closeable closeable) { try { if (closeable != null) {