mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-14 14:02:44 +00:00
adjust where things are stored, and improve migration from old version
pinned clips and library checksum are now in normal (not device protected) preferences (sensitive data) layouts and background images are now in device protected storage (required after boot)
This commit is contained in:
parent
b714fc6566
commit
89afcfd9d3
9 changed files with 179 additions and 72 deletions
|
@ -26,6 +26,7 @@ import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX
|
|||
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.MORE_KEYS_LAYOUT
|
||||
import org.dslul.openboard.inputmethod.latin.utils.MORE_KEYS_NUMBER
|
||||
import org.dslul.openboard.inputmethod.latin.utils.getLayoutFile
|
||||
import org.dslul.openboard.inputmethod.latin.utils.runInLocale
|
||||
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
||||
import java.io.File
|
||||
|
@ -202,10 +203,9 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
private fun addSymbolMoreKeys(baseKeys: MutableList<List<KeyData>>) {
|
||||
val layoutName = Settings.readSymbolsLayoutName(context, params.mId.locale)
|
||||
val layout = if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||
val file = File(context.filesDir, "layouts${File.separator}$layoutName")
|
||||
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||
else SimpleKeyboardParser(params, context)
|
||||
parser.parseCoreLayout(file.readText())
|
||||
parser.parseCoreLayout(getLayoutFile(layoutName, context).readText())
|
||||
} else {
|
||||
SimpleKeyboardParser(params, context).parseCoreLayout(context.readAssetsFile("layouts/$layoutName.txt"))
|
||||
}
|
||||
|
@ -771,10 +771,9 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
fun parseLayout(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
|
||||
val layoutName = getLayoutFileName(params, context)
|
||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||
val file = File(context.filesDir, "layouts${File.separator}$layoutName")
|
||||
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||
else SimpleKeyboardParser(params, context)
|
||||
return parser.parseLayoutString(file.readText())
|
||||
return parser.parseLayoutString(getLayoutFile(layoutName, context).readText())
|
||||
}
|
||||
val layoutFileNames = context.assets.list("layouts")!!
|
||||
if (layoutFileNames.contains("$layoutName.json")) {
|
||||
|
|
|
@ -2,24 +2,39 @@ package org.dslul.openboard.inputmethod.latin
|
|||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||
import org.dslul.openboard.inputmethod.latin.settings.USER_DICTIONARY_SUFFIX
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.Log
|
||||
import org.dslul.openboard.inputmethod.latin.utils.upgradeToolbarPref
|
||||
import java.io.File
|
||||
|
||||
class App : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
checkVersionUpgrade(this)
|
||||
app = this
|
||||
}
|
||||
|
||||
companion object {
|
||||
// used so JniUtils can access application once
|
||||
private var app: App? = null
|
||||
fun getApp(): App? {
|
||||
val application = app
|
||||
app = null
|
||||
return application
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkVersionUpgrade(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val oldVersion = prefs.getInt(Settings.PREF_VERSION_CODE, 0)
|
||||
Log.i("test", "old version $oldVersion")
|
||||
if (oldVersion == BuildConfig.VERSION_CODE)
|
||||
return
|
||||
upgradeToolbarPref(prefs)
|
||||
|
@ -33,12 +48,35 @@ fun checkVersionUpgrade(context: Context) {
|
|||
}
|
||||
}
|
||||
if (oldVersion == 0) // new install or restoring settings from old app name
|
||||
prefUpgradesWhenComingFromOldAppName(prefs)
|
||||
upgradesWhenComingFromOldAppName(context)
|
||||
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||
}
|
||||
|
||||
// todo (later): remove it when most users probably have upgraded
|
||||
private fun prefUpgradesWhenComingFromOldAppName(prefs: SharedPreferences) {
|
||||
private fun upgradesWhenComingFromOldAppName(context: Context) {
|
||||
// move layout files
|
||||
try {
|
||||
val layoutsDir = Settings.getLayoutsDir(context)
|
||||
File(context.filesDir, "layouts").listFiles()?.forEach {
|
||||
it.copyTo(File(layoutsDir, it.name), true)
|
||||
it.delete()
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
// move background images
|
||||
try {
|
||||
val bgDay = File(context.filesDir, "custom_background_image")
|
||||
if (bgDay.isFile) {
|
||||
bgDay.copyTo(Settings.getCustomBackgroundFile(context, false), true)
|
||||
bgDay.delete()
|
||||
}
|
||||
val bgNight = File(context.filesDir, "custom_background_image_night")
|
||||
if (bgNight.isFile) {
|
||||
bgNight.copyTo(Settings.getCustomBackgroundFile(context, true), true)
|
||||
bgNight.delete()
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
// upgrade prefs
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
if (prefs.all.containsKey("theme_variant")) {
|
||||
prefs.edit().putString(Settings.PREF_THEME_COLORS, prefs.getString("theme_variant", "")).apply()
|
||||
prefs.edit().remove("theme_variant").apply()
|
||||
|
@ -49,21 +87,41 @@ private fun prefUpgradesWhenComingFromOldAppName(prefs: SharedPreferences) {
|
|||
}
|
||||
prefs.all.toMap().forEach {
|
||||
if (it.key.startsWith("pref_key_") && it.key != "pref_key_longpress_timeout") {
|
||||
var remove = true
|
||||
when (val value = it.value) {
|
||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_key_"), value).apply()
|
||||
is Int -> prefs.edit().putInt(it.key.substringAfter("pref_key_"), value).apply()
|
||||
is Long -> prefs.edit().putLong(it.key.substringAfter("pref_key_"), value).apply()
|
||||
is String -> prefs.edit().putString(it.key.substringAfter("pref_key_"), value).apply()
|
||||
is Float -> prefs.edit().putFloat(it.key.substringAfter("pref_key_"), value).apply()
|
||||
else -> remove = false
|
||||
}
|
||||
if (remove)
|
||||
prefs.edit().remove(it.key).apply()
|
||||
} else if (it.key.startsWith("pref_")) {
|
||||
var remove = true
|
||||
when (val value = it.value) {
|
||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_"), value).apply()
|
||||
is Int -> prefs.edit().putInt(it.key.substringAfter("pref_"), value).apply()
|
||||
is Long -> prefs.edit().putLong(it.key.substringAfter("pref_"), value).apply()
|
||||
is String -> prefs.edit().putString(it.key.substringAfter("pref_"), value).apply()
|
||||
is Float -> prefs.edit().putFloat(it.key.substringAfter("pref_"), value).apply()
|
||||
else -> remove = false
|
||||
}
|
||||
if (remove)
|
||||
prefs.edit().remove(it.key).apply()
|
||||
}
|
||||
}
|
||||
// upgrade additional subtype locale strings
|
||||
val additionalSubtypes = mutableListOf<String>()
|
||||
Settings.readPrefAdditionalSubtypes(prefs, context.resources).split(";").forEach {
|
||||
val localeString = it.substringBefore(":")
|
||||
additionalSubtypes.add(it.replace(localeString, localeString.constructLocale().toLanguageTag()))
|
||||
}
|
||||
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.joinToString(";"))
|
||||
// move pinned clips to credential protected storage
|
||||
if (!prefs.contains(Settings.PREF_PINNED_CLIPS)) return
|
||||
val defaultPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
defaultPrefs.edit { putString(Settings.PREF_PINNED_CLIPS, prefs.getString(Settings.PREF_PINNED_CLIPS, "")) }
|
||||
prefs.edit { remove(Settings.PREF_PINNED_CLIPS) }
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package org.dslul.openboard.inputmethod.latin
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat
|
||||
|
@ -131,8 +132,9 @@ class ClipboardHistoryManager(
|
|||
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
|
||||
}
|
||||
|
||||
// pinned clips are stored in default shared preferences, not in device protected preferences!
|
||||
private fun loadPinnedClips() {
|
||||
val pinnedClipString = Settings.readPinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME))
|
||||
val pinnedClipString = Settings.readPinnedClipString(PreferenceManager.getDefaultSharedPreferences(latinIME))
|
||||
if (pinnedClipString.isEmpty()) return
|
||||
val pinnedClips: List<ClipboardHistoryEntry> = Json.decodeFromString(pinnedClipString)
|
||||
latinIME.mHandler.postUpdateClipboardPinnedClips(pinnedClips)
|
||||
|
@ -140,7 +142,7 @@ class ClipboardHistoryManager(
|
|||
|
||||
private fun savePinnedClips() {
|
||||
val pinnedClips = Json.encodeToString(historyEntries.filter { it.isPinned })
|
||||
Settings.writePinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME), pinnedClips)
|
||||
Settings.writePinnedClipString(PreferenceManager.getDefaultSharedPreferences(latinIME), pinnedClips)
|
||||
}
|
||||
|
||||
interface OnHistoryChangeListener {
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.dslul.openboard.inputmethod.latin.utils.Log
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.dslul.openboard.inputmethod.compat.locale
|
||||
|
@ -33,6 +34,7 @@ import org.dslul.openboard.inputmethod.latin.common.FileUtils
|
|||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
|
||||
import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.JniUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.editCustomLayout
|
||||
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
|
||||
|
@ -117,13 +119,15 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
|
||||
findPreference<Preference>("custom_symbols_layout")?.setOnPreferenceClickListener {
|
||||
val layoutName = Settings.readSymbolsLayoutName(context, context.resources.configuration.locale()).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) }
|
||||
val oldLayout = if (layoutName != null) null else context.assets.open("layouts${File.separator}symbols.txt").reader().readText()
|
||||
val oldLayout = if (layoutName != null) null
|
||||
else context.assets.open("layouts${File.separator}symbols.txt").reader().readText()
|
||||
editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}symbols.txt", context, oldLayout, true)
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("custom_shift_symbols_layout")?.setOnPreferenceClickListener {
|
||||
val layoutName = Settings.readShiftedSymbolsLayoutName(context).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) }
|
||||
val oldLayout = if (layoutName != null) null else context.assets.open("layouts${File.separator}symbols_shifted.txt").reader().readText()
|
||||
val oldLayout = if (layoutName != null) null
|
||||
else context.assets.open("layouts${File.separator}symbols_shifted.txt").reader().readText()
|
||||
editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}shift_symbols.txt", context, oldLayout, true)
|
||||
true
|
||||
}
|
||||
|
@ -136,6 +140,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun onClickLoadLibrary(): Boolean {
|
||||
// get architecture for telling user which file to use
|
||||
val abi = Build.SUPPORTED_ABIS[0]
|
||||
|
@ -153,6 +158,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
if (libfile.exists()) {
|
||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||
libfile.delete()
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().remove(Settings.PREF_LIBRARY_CHECKSUM).commit()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +245,8 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
@SuppressLint("ApplySharedPref")
|
||||
private fun renameToLibfileAndRestart(file: File, checksum: String) {
|
||||
libfile.delete()
|
||||
sharedPreferences.edit().putString("lib_checksum", checksum).commit()
|
||||
// store checksum in default preferences (soo JniUtils)
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().putString(Settings.PREF_LIBRARY_CHECKSUM, checksum).commit()
|
||||
file.renameTo(libfile)
|
||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
||||
}
|
||||
|
@ -281,6 +288,14 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
if (backupFilePatterns.any { path.matches(it) })
|
||||
files.add(file)
|
||||
}
|
||||
val protectedFilesDir = DeviceProtectedUtils.getDeviceProtectedContext(requireContext()).filesDir
|
||||
val protectedFilesPath = protectedFilesDir.path + File.separator
|
||||
val protectedFiles = mutableListOf<File>()
|
||||
protectedFilesDir.walk().forEach { file ->
|
||||
val path = file.path.replace(protectedFilesPath, "")
|
||||
if (backupFilePatterns.any { path.matches(it) })
|
||||
protectedFiles.add(file)
|
||||
}
|
||||
try {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
// write files to zip
|
||||
|
@ -292,8 +307,17 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
fileStream.close()
|
||||
zipStream.closeEntry()
|
||||
}
|
||||
protectedFiles.forEach {
|
||||
val fileStream = FileInputStream(it).buffered()
|
||||
zipStream.putNextEntry(ZipEntry(it.path.replace(protectedFilesDir.path, "unprotected")))
|
||||
fileStream.copyTo(zipStream, 1024)
|
||||
fileStream.close()
|
||||
zipStream.closeEntry()
|
||||
}
|
||||
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
||||
zipStream.bufferedWriter().use { settingsToJsonStream(sharedPreferences.all, it) }
|
||||
zipStream.putNextEntry(ZipEntry(PROTECTED_PREFS_FILE_NAME))
|
||||
zipStream.bufferedWriter().use { settingsToJsonStream(PreferenceManager.getDefaultSharedPreferences(requireContext()).all, it) }
|
||||
zipStream.close()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
|
@ -310,7 +334,14 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
var entry: ZipEntry? = zip.nextEntry
|
||||
val filesDir = requireContext().filesDir?.path ?: return
|
||||
while (entry != null) {
|
||||
if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||
if (entry.name.startsWith("unprotected")) {
|
||||
val adjustedName = entry.name.substringAfter("unprotected${File.separator}")
|
||||
if (backupFilePatterns.any { adjustedName.matches(it) }) {
|
||||
val targetFileName = upgradeFileNames(adjustedName)
|
||||
val file = File(filesDir, targetFileName)
|
||||
FileUtils.copyStreamToNewFile(zip, file)
|
||||
}
|
||||
} else if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||
val targetFileName = upgradeFileNames(entry.name)
|
||||
val file = File(filesDir, targetFileName)
|
||||
FileUtils.copyStreamToNewFile(zip, file)
|
||||
|
@ -318,6 +349,10 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
val prefLines = String(zip.readBytes()).split("\n")
|
||||
sharedPreferences.edit().clear().apply()
|
||||
readJsonLinesToSettings(prefLines)
|
||||
} else if (entry.name == PROTECTED_PREFS_FILE_NAME) {
|
||||
val prefLines = String(zip.readBytes()).split("\n")
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().clear().apply()
|
||||
readJsonLinesToSettings(prefLines)
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
|
@ -442,4 +477,5 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
}
|
||||
|
||||
private const val PREFS_FILE_NAME = "preferences.json"
|
||||
private const val PROTECTED_PREFS_FILE_NAME = "protected_preferences.json"
|
||||
private const val TAG = "AdvancedSettingsFragment"
|
||||
|
|
|
@ -155,6 +155,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
// used as a workaround against keyboard not showing edited theme in ColorsSettingsFragment
|
||||
public static final String PREF_FORCE_OPPOSITE_THEME = "force_opposite_theme";
|
||||
public static final String PREF_SHOW_ALL_COLORS = "show_all_colors";
|
||||
public static final String PREF_LIBRARY_CHECKSUM = "lib_checksum";
|
||||
|
||||
private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f;
|
||||
private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1;
|
||||
|
@ -251,13 +252,11 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
// Accessed from the settings interface, hence public
|
||||
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, final Resources res) {
|
||||
return prefs.getBoolean(PREF_SOUND_ON, res.getBoolean(R.bool.config_default_sound_enabled));
|
||||
}
|
||||
|
||||
public static boolean readVibrationEnabled(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static boolean readVibrationEnabled(final SharedPreferences prefs, final Resources res) {
|
||||
return prefs.getBoolean(PREF_VIBRATE_ON, res.getBoolean(R.bool.config_default_vibration_enabled))
|
||||
&& AudioAndHapticFeedbackManager.getInstance().hasVibrator();
|
||||
}
|
||||
|
@ -274,14 +273,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
mPrefs.edit().putBoolean(Settings.PREF_AUTO_CORRECTION, !readAutoCorrectEnabled(mPrefs)).apply();
|
||||
}
|
||||
|
||||
public static String readAutoCorrectConfidence(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static String readAutoCorrectConfidence(final SharedPreferences prefs, final Resources res) {
|
||||
return prefs.getString(PREF_AUTO_CORRECTION_CONFIDENCE,
|
||||
res.getString(R.string.auto_correction_threshold_mode_index_modest));
|
||||
}
|
||||
|
||||
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, final Resources res) {
|
||||
return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,
|
||||
res.getBoolean(R.bool.config_block_potentially_offensive));
|
||||
}
|
||||
|
@ -294,8 +291,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
|
||||
}
|
||||
|
||||
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs, final Resources res) {
|
||||
final boolean defaultKeyPreviewPopup = res.getBoolean(
|
||||
R.bool.config_default_key_preview_popup);
|
||||
if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
|
||||
|
@ -313,20 +309,17 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
|
||||
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs, final Resources res) {
|
||||
final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
|
||||
res.getStringArray(R.array.predefined_subtypes));
|
||||
return prefs.getString(PREF_ADDITIONAL_SUBTYPES, predefinedPrefSubtypes);
|
||||
}
|
||||
|
||||
public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
|
||||
final String prefSubtypes) {
|
||||
public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, final String prefSubtypes) {
|
||||
prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply();
|
||||
}
|
||||
|
||||
public static float readKeypressSoundVolume(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static float readKeypressSoundVolume(final SharedPreferences prefs, final Resources res) {
|
||||
final float volume = prefs.getFloat(
|
||||
PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT);
|
||||
return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume
|
||||
|
@ -342,8 +335,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME));
|
||||
}
|
||||
|
||||
public static int readKeyLongpressTimeout(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static int readKeyLongpressTimeout(final SharedPreferences prefs, final Resources res) {
|
||||
final int milliseconds = prefs.getInt(
|
||||
PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
|
||||
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
|
||||
|
@ -354,8 +346,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return res.getInteger(R.integer.config_default_longpress_key_timeout);
|
||||
}
|
||||
|
||||
public static int readKeypressVibrationDuration(final SharedPreferences prefs,
|
||||
final Resources res) {
|
||||
public static int readKeypressVibrationDuration(final SharedPreferences prefs, final Resources res) {
|
||||
final int milliseconds = prefs.getInt(
|
||||
PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT);
|
||||
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
|
||||
|
@ -420,7 +411,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public void writeOneHandedModeEnabled(final boolean enabled) {
|
||||
mPrefs.edit().putBoolean(PREF_ONE_HANDED_MODE_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), enabled).apply();
|
||||
mPrefs.edit().putBoolean(PREF_ONE_HANDED_MODE_PREFIX +
|
||||
(getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), enabled).apply();
|
||||
}
|
||||
|
||||
public static float readOneHandedModeScale(final SharedPreferences prefs, final boolean portrait) {
|
||||
|
@ -428,7 +420,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public void writeOneHandedModeScale(final Float scale) {
|
||||
mPrefs.edit().putFloat(PREF_ONE_HANDED_SCALE_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), scale).apply();
|
||||
mPrefs.edit().putFloat(PREF_ONE_HANDED_SCALE_PREFIX +
|
||||
(getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), scale).apply();
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
|
@ -437,7 +430,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public void writeOneHandedModeGravity(final int gravity) {
|
||||
mPrefs.edit().putInt(PREF_ONE_HANDED_GRAVITY_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), gravity).apply();
|
||||
mPrefs.edit().putInt(PREF_ONE_HANDED_GRAVITY_PREFIX +
|
||||
(getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), gravity).apply();
|
||||
}
|
||||
|
||||
public static boolean readHasHardwareKeyboard(final Configuration conf) {
|
||||
|
@ -517,7 +511,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public static String readSymbolsLayoutName(final Context context, final Locale locale) {
|
||||
String[] layouts = new File(context.getFilesDir(), "layouts").list();
|
||||
String[] layouts = getLayoutsDir(context).list();
|
||||
if (layouts != null) {
|
||||
for (String name : layouts) {
|
||||
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "symbols"))
|
||||
|
@ -528,7 +522,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public static String readShiftedSymbolsLayoutName(final Context context) {
|
||||
String[] layouts = new File(context.getFilesDir(), "layouts").list();
|
||||
String[] layouts = getLayoutsDir(context).list();
|
||||
if (layouts != null) {
|
||||
for (String name : layouts) {
|
||||
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "shift_symbols"))
|
||||
|
@ -538,6 +532,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return "symbols_shifted";
|
||||
}
|
||||
|
||||
public static File getLayoutsDir(final Context context) {
|
||||
return new File(DeviceProtectedUtils.getDeviceProtectedContext(context).getFilesDir(), "layouts");
|
||||
}
|
||||
|
||||
@Nullable public static Drawable readUserBackgroundImage(final Context context, final boolean night) {
|
||||
if (night && sCachedBackgroundNight != null) return sCachedBackgroundNight;
|
||||
if (!night && sCachedBackgroundDay != null) return sCachedBackgroundDay;
|
||||
|
@ -557,7 +555,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
}
|
||||
|
||||
public static File getCustomBackgroundFile(final Context context, final boolean night) {
|
||||
return new File(context.getFilesDir(), "custom_background_image" + (night ? "_night" : ""));
|
||||
return new File(DeviceProtectedUtils.getDeviceProtectedContext(context).getFilesDir(),
|
||||
"custom_background_image" + (night ? "_night" : ""));
|
||||
}
|
||||
|
||||
public static boolean readDayNightPref(final SharedPreferences prefs, final Resources res) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.MORE_KE
|
|||
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.SimpleKeyboardParser
|
||||
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
|
@ -61,7 +62,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
// name must be encoded to avoid issues with validity of subtype extra string or file name
|
||||
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}.${if (isJson) "json" else "txt"}"
|
||||
val file = getFile(name, context)
|
||||
val file = getLayoutFile(name, context)
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
file.parentFile?.mkdir()
|
||||
|
@ -119,8 +120,8 @@ private fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
|
|||
return true
|
||||
}
|
||||
|
||||
private fun getFile(layoutName: String, context: Context) =
|
||||
File(context.filesDir, "layouts${File.separator}$layoutName")
|
||||
fun getLayoutFile(layoutName: String, context: Context) =
|
||||
File(Settings.getLayoutsDir(context), layoutName)
|
||||
|
||||
// undo the name changes in loadCustomLayout when clicking ok
|
||||
fun getLayoutDisplayName(layoutName: String) =
|
||||
|
@ -131,11 +132,11 @@ fun getLayoutDisplayName(layoutName: String) =
|
|||
}
|
||||
|
||||
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
||||
getFile(layoutName, context).delete()
|
||||
getLayoutFile(layoutName, context).delete()
|
||||
}
|
||||
|
||||
fun editCustomLayout(layoutName: String, context: Context, startContent: String? = null, isSymbols: Boolean = false) {
|
||||
val file = getFile(layoutName, context)
|
||||
val file = getLayoutFile(layoutName, context)
|
||||
val editText = EditText(context).apply {
|
||||
setText(startContent ?: file.readText())
|
||||
}
|
||||
|
|
|
@ -9,9 +9,8 @@ package org.dslul.openboard.inputmethod.latin.utils;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public final class DeviceProtectedUtils {
|
||||
|
||||
|
@ -29,15 +28,14 @@ public final class DeviceProtectedUtils {
|
|||
prefs = PreferenceManager.getDefaultSharedPreferences(deviceProtectedContext);
|
||||
if (prefs.getAll().isEmpty()) {
|
||||
Log.i(TAG, "Device encrypted storage is empty, copying values from credential encrypted storage");
|
||||
deviceProtectedContext.moveSharedPreferencesFrom(context, PreferenceManager.getDefaultSharedPreferencesName(context));
|
||||
deviceProtectedContext.moveSharedPreferencesFrom(context, android.preference.PreferenceManager.getDefaultSharedPreferencesName(context));
|
||||
}
|
||||
return prefs;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
private static Context getDeviceProtectedContext(final Context context) {
|
||||
return context.isDeviceProtectedStorage()
|
||||
? context : context.createDeviceProtectedStorageContext();
|
||||
public static Context getDeviceProtectedContext(final Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return context;
|
||||
return context.isDeviceProtectedStorage() ? context : context.createDeviceProtectedStorageContext();
|
||||
}
|
||||
|
||||
private DeviceProtectedUtils() {
|
||||
|
|
|
@ -11,7 +11,12 @@ import android.app.Application;
|
|||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.App;
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
|
@ -40,18 +45,28 @@ public final class JniUtils {
|
|||
static {
|
||||
// hardcoded default path, may not work on all phones
|
||||
@SuppressLint("SdCardPath") String filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
||||
Application app = null;
|
||||
try {
|
||||
// try using reflection to get (app)context: https://stackoverflow.com/a/38967293
|
||||
app = (Application) Class.forName("android.app.ActivityThread")
|
||||
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
||||
// and use the actual path if possible
|
||||
Application app = App.Companion.getApp();
|
||||
if (app == null) {
|
||||
try {
|
||||
// try using reflection to get (app)context: https://stackoverflow.com/a/38967293
|
||||
// this may not be necessary any more, now that we get the app somewhere else?
|
||||
app = (Application) Class.forName("android.app.ActivityThread")
|
||||
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
if (app != null) // use the actual path if possible
|
||||
filesDir = app.getFilesDir().getAbsolutePath();
|
||||
} catch (Exception ignored) { }
|
||||
final File userSuppliedLibrary = new File(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
||||
if (userSuppliedLibrary.exists()) {
|
||||
final String wantedChecksum = app == null ? expectedDefaultChecksum() : DeviceProtectedUtils.getSharedPreferences(app).getString("lib_checksum", "");
|
||||
String wantedChecksum = expectedDefaultChecksum();
|
||||
try {
|
||||
if (app != null) {
|
||||
// we want the default preferences, because storing the checksum in device protected storage is discouraged
|
||||
// see https://developer.android.com/reference/android/content/Context#createDeviceProtectedStorageContext()
|
||||
// todo: test what happens on an encrypted phone after reboot (don't have one...)
|
||||
// does the app restart after unlocking, or is gesture typing unavailable?
|
||||
wantedChecksum = PreferenceManager.getDefaultSharedPreferences(app).getString(Settings.PREF_LIBRARY_CHECKSUM, wantedChecksum);
|
||||
}
|
||||
final String checksum = ChecksumCalculator.INSTANCE.checksum(new FileInputStream(userSuppliedLibrary));
|
||||
if (TextUtils.equals(wantedChecksum, checksum)) {
|
||||
// try loading the library
|
||||
|
@ -59,11 +74,9 @@ public final class JniUtils {
|
|||
sHaveGestureLib = true; // this is an assumption, any way to actually check?
|
||||
} else {
|
||||
// delete if checksum doesn't match
|
||||
// this actually is bad if we can't get the application and the user has a different library than expected
|
||||
// todo: until the app is renamed, we continue loading the library anyway
|
||||
// userSuppliedLibrary.delete();
|
||||
System.load(userSuppliedLibrary.getAbsolutePath());
|
||||
sHaveGestureLib = true;
|
||||
// this is bad if we can't get the application and the user has a different library than expected...
|
||||
userSuppliedLibrary.delete();
|
||||
sHaveGestureLib = false;
|
||||
}
|
||||
} catch (Throwable t) { // catch everything, maybe provided library simply doesn't work
|
||||
Log.w(TAG, "Could not load user-supplied library", t);
|
||||
|
|
|
@ -154,6 +154,7 @@ fun getAvailableSubtypeLocales(): Collection<Locale> {
|
|||
fun reloadEnabledSubtypes(context: Context) {
|
||||
require(initialized)
|
||||
enabledSubtypes.clear()
|
||||
removeInvalidCustomSubtypes(context)
|
||||
loadEnabledSubtypes(context)
|
||||
}
|
||||
|
||||
|
@ -233,18 +234,11 @@ private fun loadResourceSubtypes(resources: Resources) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadAdditionalSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val additionalSubtypeString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
|
||||
val subtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypeString)
|
||||
additionalSubtypes.addAll(subtypes)
|
||||
}
|
||||
|
||||
// remove custom subtypes without a layout file
|
||||
private fun removeInvalidCustomSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val additionalSubtypes = Settings.readPrefAdditionalSubtypes(prefs, context.resources).split(";")
|
||||
val customSubtypeFiles by lazy { File(context.filesDir, "layouts").list() }
|
||||
val customSubtypeFiles by lazy { Settings.getLayoutsDir(context).list() }
|
||||
val subtypesToRemove = mutableListOf<String>()
|
||||
additionalSubtypes.forEach {
|
||||
val name = it.substringAfter(":").substringBefore(":")
|
||||
|
@ -257,6 +251,13 @@ private fun removeInvalidCustomSubtypes(context: Context) {
|
|||
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.filterNot { it in subtypesToRemove }.joinToString(";"))
|
||||
}
|
||||
|
||||
private fun loadAdditionalSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val additionalSubtypeString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
|
||||
val subtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypeString)
|
||||
additionalSubtypes.addAll(subtypes)
|
||||
}
|
||||
|
||||
// requires loadResourceSubtypes to be called before
|
||||
private fun loadEnabledSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue