store pinned clips in shared prferences instead of separate file

This commit is contained in:
Helium314 2023-11-04 13:38:24 +01:00
parent aad8c68bf9
commit 2dc9b12b13
4 changed files with 58 additions and 135 deletions

View file

@ -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<ClipboardHistoryEntry> {
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<CharSequence> {
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()
}

View file

@ -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<ClipboardHistoryEntry>
private val historyEntries: MutableList<ClipboardHistoryEntry> = 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<ClipboardHistoryEntry>) {
@ -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<ClipboardHistoryEntry> = 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<ClipboardHistoryEntry>()
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<ClipboardHistoryEntry>) {
// 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()
}
}

View file

@ -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<String> 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<String> readPinnedKeys(final SharedPreferences prefs) {
final String pinnedKeysString = prefs.getString(Settings.PREF_PINNED_KEYS, "");
if (pinnedKeysString.isEmpty())

View file

@ -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<ClipboardHistoryEntry> jsonBytesToHistoryEntryList(final byte[] bytes) {
final ArrayList<ClipboardHistoryEntry> 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<ClipboardHistoryEntry> 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) {