mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-15 06:22:45 +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.InputTypeUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.MORE_KEYS_LAYOUT
|
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.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.runInLocale
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
||||||
import java.io.File
|
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>>) {
|
private fun addSymbolMoreKeys(baseKeys: MutableList<List<KeyData>>) {
|
||||||
val layoutName = Settings.readSymbolsLayoutName(context, params.mId.locale)
|
val layoutName = Settings.readSymbolsLayoutName(context, params.mId.locale)
|
||||||
val layout = if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
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)
|
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||||
else SimpleKeyboardParser(params, context)
|
else SimpleKeyboardParser(params, context)
|
||||||
parser.parseCoreLayout(file.readText())
|
parser.parseCoreLayout(getLayoutFile(layoutName, context).readText())
|
||||||
} else {
|
} else {
|
||||||
SimpleKeyboardParser(params, context).parseCoreLayout(context.readAssetsFile("layouts/$layoutName.txt"))
|
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>> {
|
fun parseLayout(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
|
||||||
val layoutName = getLayoutFileName(params, context)
|
val layoutName = getLayoutFileName(params, context)
|
||||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||||
val file = File(context.filesDir, "layouts${File.separator}$layoutName")
|
|
||||||
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||||
else SimpleKeyboardParser(params, context)
|
else SimpleKeyboardParser(params, context)
|
||||||
return parser.parseLayoutString(file.readText())
|
return parser.parseLayoutString(getLayoutFile(layoutName, context).readText())
|
||||||
}
|
}
|
||||||
val layoutFileNames = context.assets.list("layouts")!!
|
val layoutFileNames = context.assets.list("layouts")!!
|
||||||
if (layoutFileNames.contains("$layoutName.json")) {
|
if (layoutFileNames.contains("$layoutName.json")) {
|
||||||
|
|
|
@ -2,24 +2,39 @@ package org.dslul.openboard.inputmethod.latin
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.core.content.edit
|
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.Settings
|
||||||
import org.dslul.openboard.inputmethod.latin.settings.USER_DICTIONARY_SUFFIX
|
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.DeviceProtectedUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils
|
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 org.dslul.openboard.inputmethod.latin.utils.upgradeToolbarPref
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
checkVersionUpgrade(this)
|
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) {
|
fun checkVersionUpgrade(context: Context) {
|
||||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||||
val oldVersion = prefs.getInt(Settings.PREF_VERSION_CODE, 0)
|
val oldVersion = prefs.getInt(Settings.PREF_VERSION_CODE, 0)
|
||||||
|
Log.i("test", "old version $oldVersion")
|
||||||
if (oldVersion == BuildConfig.VERSION_CODE)
|
if (oldVersion == BuildConfig.VERSION_CODE)
|
||||||
return
|
return
|
||||||
upgradeToolbarPref(prefs)
|
upgradeToolbarPref(prefs)
|
||||||
|
@ -33,12 +48,35 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion == 0) // new install or restoring settings from old app name
|
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) }
|
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo (later): remove it when most users probably have upgraded
|
// 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")) {
|
if (prefs.all.containsKey("theme_variant")) {
|
||||||
prefs.edit().putString(Settings.PREF_THEME_COLORS, prefs.getString("theme_variant", "")).apply()
|
prefs.edit().putString(Settings.PREF_THEME_COLORS, prefs.getString("theme_variant", "")).apply()
|
||||||
prefs.edit().remove("theme_variant").apply()
|
prefs.edit().remove("theme_variant").apply()
|
||||||
|
@ -49,21 +87,41 @@ private fun prefUpgradesWhenComingFromOldAppName(prefs: SharedPreferences) {
|
||||||
}
|
}
|
||||||
prefs.all.toMap().forEach {
|
prefs.all.toMap().forEach {
|
||||||
if (it.key.startsWith("pref_key_") && it.key != "pref_key_longpress_timeout") {
|
if (it.key.startsWith("pref_key_") && it.key != "pref_key_longpress_timeout") {
|
||||||
|
var remove = true
|
||||||
when (val value = it.value) {
|
when (val value = it.value) {
|
||||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_key_"), value).apply()
|
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 Int -> prefs.edit().putInt(it.key.substringAfter("pref_key_"), value).apply()
|
||||||
is Long -> prefs.edit().putLong(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 String -> prefs.edit().putString(it.key.substringAfter("pref_key_"), value).apply()
|
||||||
is Float -> prefs.edit().putFloat(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_")) {
|
} else if (it.key.startsWith("pref_")) {
|
||||||
|
var remove = true
|
||||||
when (val value = it.value) {
|
when (val value = it.value) {
|
||||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_"), value).apply()
|
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_"), value).apply()
|
||||||
is Int -> prefs.edit().putInt(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 Long -> prefs.edit().putLong(it.key.substringAfter("pref_"), value).apply()
|
||||||
is String -> prefs.edit().putString(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()
|
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.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat
|
import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat
|
||||||
|
@ -131,8 +132,9 @@ class ClipboardHistoryManager(
|
||||||
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
|
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pinned clips are stored in default shared preferences, not in device protected preferences!
|
||||||
private fun loadPinnedClips() {
|
private fun loadPinnedClips() {
|
||||||
val pinnedClipString = Settings.readPinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME))
|
val pinnedClipString = Settings.readPinnedClipString(PreferenceManager.getDefaultSharedPreferences(latinIME))
|
||||||
if (pinnedClipString.isEmpty()) return
|
if (pinnedClipString.isEmpty()) return
|
||||||
val pinnedClips: List<ClipboardHistoryEntry> = Json.decodeFromString(pinnedClipString)
|
val pinnedClips: List<ClipboardHistoryEntry> = Json.decodeFromString(pinnedClipString)
|
||||||
latinIME.mHandler.postUpdateClipboardPinnedClips(pinnedClips)
|
latinIME.mHandler.postUpdateClipboardPinnedClips(pinnedClips)
|
||||||
|
@ -140,7 +142,7 @@ class ClipboardHistoryManager(
|
||||||
|
|
||||||
private fun savePinnedClips() {
|
private fun savePinnedClips() {
|
||||||
val pinnedClips = Json.encodeToString(historyEntries.filter { it.isPinned })
|
val pinnedClips = Json.encodeToString(historyEntries.filter { it.isPinned })
|
||||||
Settings.writePinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME), pinnedClips)
|
Settings.writePinnedClipString(PreferenceManager.getDefaultSharedPreferences(latinIME), pinnedClips)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnHistoryChangeListener {
|
interface OnHistoryChangeListener {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.dslul.openboard.inputmethod.latin.utils.Log
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.dslul.openboard.inputmethod.compat.locale
|
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.common.LocaleUtils.constructLocale
|
||||||
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
|
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.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.JniUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.editCustomLayout
|
import org.dslul.openboard.inputmethod.latin.utils.editCustomLayout
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
|
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
|
||||||
|
@ -117,13 +119,15 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
|
|
||||||
findPreference<Preference>("custom_symbols_layout")?.setOnPreferenceClickListener {
|
findPreference<Preference>("custom_symbols_layout")?.setOnPreferenceClickListener {
|
||||||
val layoutName = Settings.readSymbolsLayoutName(context, context.resources.configuration.locale()).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) }
|
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)
|
editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}symbols.txt", context, oldLayout, true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
findPreference<Preference>("custom_shift_symbols_layout")?.setOnPreferenceClickListener {
|
findPreference<Preference>("custom_shift_symbols_layout")?.setOnPreferenceClickListener {
|
||||||
val layoutName = Settings.readShiftedSymbolsLayoutName(context).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) }
|
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)
|
editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}shift_symbols.txt", context, oldLayout, true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -136,6 +140,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
private fun onClickLoadLibrary(): Boolean {
|
private fun onClickLoadLibrary(): Boolean {
|
||||||
// get architecture for telling user which file to use
|
// get architecture for telling user which file to use
|
||||||
val abi = Build.SUPPORTED_ABIS[0]
|
val abi = Build.SUPPORTED_ABIS[0]
|
||||||
|
@ -153,6 +158,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
if (libfile.exists()) {
|
if (libfile.exists()) {
|
||||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||||
libfile.delete()
|
libfile.delete()
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().remove(Settings.PREF_LIBRARY_CHECKSUM).commit()
|
||||||
Runtime.getRuntime().exit(0)
|
Runtime.getRuntime().exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +245,8 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
@SuppressLint("ApplySharedPref")
|
@SuppressLint("ApplySharedPref")
|
||||||
private fun renameToLibfileAndRestart(file: File, checksum: String) {
|
private fun renameToLibfileAndRestart(file: File, checksum: String) {
|
||||||
libfile.delete()
|
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)
|
file.renameTo(libfile)
|
||||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
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) })
|
if (backupFilePatterns.any { path.matches(it) })
|
||||||
files.add(file)
|
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 {
|
try {
|
||||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||||
// write files to zip
|
// write files to zip
|
||||||
|
@ -292,8 +307,17 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
fileStream.close()
|
fileStream.close()
|
||||||
zipStream.closeEntry()
|
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.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
||||||
zipStream.bufferedWriter().use { settingsToJsonStream(sharedPreferences.all, it) }
|
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()
|
zipStream.close()
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
@ -310,7 +334,14 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
var entry: ZipEntry? = zip.nextEntry
|
var entry: ZipEntry? = zip.nextEntry
|
||||||
val filesDir = requireContext().filesDir?.path ?: return
|
val filesDir = requireContext().filesDir?.path ?: return
|
||||||
while (entry != null) {
|
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 targetFileName = upgradeFileNames(entry.name)
|
||||||
val file = File(filesDir, targetFileName)
|
val file = File(filesDir, targetFileName)
|
||||||
FileUtils.copyStreamToNewFile(zip, file)
|
FileUtils.copyStreamToNewFile(zip, file)
|
||||||
|
@ -318,6 +349,10 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
val prefLines = String(zip.readBytes()).split("\n")
|
val prefLines = String(zip.readBytes()).split("\n")
|
||||||
sharedPreferences.edit().clear().apply()
|
sharedPreferences.edit().clear().apply()
|
||||||
readJsonLinesToSettings(prefLines)
|
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()
|
zip.closeEntry()
|
||||||
entry = zip.nextEntry
|
entry = zip.nextEntry
|
||||||
|
@ -442,4 +477,5 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val PREFS_FILE_NAME = "preferences.json"
|
private const val PREFS_FILE_NAME = "preferences.json"
|
||||||
|
private const val PROTECTED_PREFS_FILE_NAME = "protected_preferences.json"
|
||||||
private const val TAG = "AdvancedSettingsFragment"
|
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
|
// 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_FORCE_OPPOSITE_THEME = "force_opposite_theme";
|
||||||
public static final String PREF_SHOW_ALL_COLORS = "show_all_colors";
|
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 float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f;
|
||||||
private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1;
|
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
|
// Accessed from the settings interface, hence public
|
||||||
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
|
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
return prefs.getBoolean(PREF_SOUND_ON, res.getBoolean(R.bool.config_default_sound_enabled));
|
return prefs.getBoolean(PREF_SOUND_ON, res.getBoolean(R.bool.config_default_sound_enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean readVibrationEnabled(final SharedPreferences prefs,
|
public static boolean readVibrationEnabled(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
return prefs.getBoolean(PREF_VIBRATE_ON, res.getBoolean(R.bool.config_default_vibration_enabled))
|
return prefs.getBoolean(PREF_VIBRATE_ON, res.getBoolean(R.bool.config_default_vibration_enabled))
|
||||||
&& AudioAndHapticFeedbackManager.getInstance().hasVibrator();
|
&& AudioAndHapticFeedbackManager.getInstance().hasVibrator();
|
||||||
}
|
}
|
||||||
|
@ -274,14 +273,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
mPrefs.edit().putBoolean(Settings.PREF_AUTO_CORRECTION, !readAutoCorrectEnabled(mPrefs)).apply();
|
mPrefs.edit().putBoolean(Settings.PREF_AUTO_CORRECTION, !readAutoCorrectEnabled(mPrefs)).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readAutoCorrectConfidence(final SharedPreferences prefs,
|
public static String readAutoCorrectConfidence(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
return prefs.getString(PREF_AUTO_CORRECTION_CONFIDENCE,
|
return prefs.getString(PREF_AUTO_CORRECTION_CONFIDENCE,
|
||||||
res.getString(R.string.auto_correction_threshold_mode_index_modest));
|
res.getString(R.string.auto_correction_threshold_mode_index_modest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
|
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,
|
return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,
|
||||||
res.getBoolean(R.bool.config_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);
|
return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
|
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
final boolean defaultKeyPreviewPopup = res.getBoolean(
|
final boolean defaultKeyPreviewPopup = res.getBoolean(
|
||||||
R.bool.config_default_key_preview_popup);
|
R.bool.config_default_key_preview_popup);
|
||||||
if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
|
if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
|
||||||
|
@ -313,20 +309,17 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
|
public static String readPrefAdditionalSubtypes(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
|
final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
|
||||||
res.getStringArray(R.array.predefined_subtypes));
|
res.getStringArray(R.array.predefined_subtypes));
|
||||||
return prefs.getString(PREF_ADDITIONAL_SUBTYPES, predefinedPrefSubtypes);
|
return prefs.getString(PREF_ADDITIONAL_SUBTYPES, predefinedPrefSubtypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
|
public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, final String prefSubtypes) {
|
||||||
final String prefSubtypes) {
|
|
||||||
prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply();
|
prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float readKeypressSoundVolume(final SharedPreferences prefs,
|
public static float readKeypressSoundVolume(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
final float volume = prefs.getFloat(
|
final float volume = prefs.getFloat(
|
||||||
PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT);
|
PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT);
|
||||||
return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume
|
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));
|
R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int readKeyLongpressTimeout(final SharedPreferences prefs,
|
public static int readKeyLongpressTimeout(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
final int milliseconds = prefs.getInt(
|
final int milliseconds = prefs.getInt(
|
||||||
PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
|
PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
|
||||||
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
|
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);
|
return res.getInteger(R.integer.config_default_longpress_key_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int readKeypressVibrationDuration(final SharedPreferences prefs,
|
public static int readKeypressVibrationDuration(final SharedPreferences prefs, final Resources res) {
|
||||||
final Resources res) {
|
|
||||||
final int milliseconds = prefs.getInt(
|
final int milliseconds = prefs.getInt(
|
||||||
PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT);
|
PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT);
|
||||||
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
|
return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
|
||||||
|
@ -420,7 +411,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeOneHandedModeEnabled(final boolean enabled) {
|
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) {
|
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) {
|
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")
|
@SuppressLint("RtlHardcoded")
|
||||||
|
@ -437,7 +430,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeOneHandedModeGravity(final int gravity) {
|
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) {
|
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) {
|
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) {
|
if (layouts != null) {
|
||||||
for (String name : layouts) {
|
for (String name : layouts) {
|
||||||
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "symbols"))
|
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) {
|
public static String readShiftedSymbolsLayoutName(final Context context) {
|
||||||
String[] layouts = new File(context.getFilesDir(), "layouts").list();
|
String[] layouts = getLayoutsDir(context).list();
|
||||||
if (layouts != null) {
|
if (layouts != null) {
|
||||||
for (String name : layouts) {
|
for (String name : layouts) {
|
||||||
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "shift_symbols"))
|
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "shift_symbols"))
|
||||||
|
@ -538,6 +532,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
return "symbols_shifted";
|
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) {
|
@Nullable public static Drawable readUserBackgroundImage(final Context context, final boolean night) {
|
||||||
if (night && sCachedBackgroundNight != null) return sCachedBackgroundNight;
|
if (night && sCachedBackgroundNight != null) return sCachedBackgroundNight;
|
||||||
if (!night && sCachedBackgroundDay != null) return sCachedBackgroundDay;
|
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) {
|
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) {
|
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.SimpleKeyboardParser
|
||||||
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||||
import org.dslul.openboard.inputmethod.latin.R
|
import org.dslul.openboard.inputmethod.latin.R
|
||||||
|
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
@ -61,7 +62,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
// name must be encoded to avoid issues with validity of subtype extra string or file name
|
// 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"}"
|
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())
|
if (file.exists())
|
||||||
file.delete()
|
file.delete()
|
||||||
file.parentFile?.mkdir()
|
file.parentFile?.mkdir()
|
||||||
|
@ -119,8 +120,8 @@ private fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFile(layoutName: String, context: Context) =
|
fun getLayoutFile(layoutName: String, context: Context) =
|
||||||
File(context.filesDir, "layouts${File.separator}$layoutName")
|
File(Settings.getLayoutsDir(context), layoutName)
|
||||||
|
|
||||||
// undo the name changes in loadCustomLayout when clicking ok
|
// undo the name changes in loadCustomLayout when clicking ok
|
||||||
fun getLayoutDisplayName(layoutName: String) =
|
fun getLayoutDisplayName(layoutName: String) =
|
||||||
|
@ -131,11 +132,11 @@ fun getLayoutDisplayName(layoutName: String) =
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
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) {
|
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 {
|
val editText = EditText(context).apply {
|
||||||
setText(startContent ?: file.readText())
|
setText(startContent ?: file.readText())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,8 @@ package org.dslul.openboard.inputmethod.latin.utils;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
public final class DeviceProtectedUtils {
|
public final class DeviceProtectedUtils {
|
||||||
|
|
||||||
|
@ -29,15 +28,14 @@ public final class DeviceProtectedUtils {
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(deviceProtectedContext);
|
prefs = PreferenceManager.getDefaultSharedPreferences(deviceProtectedContext);
|
||||||
if (prefs.getAll().isEmpty()) {
|
if (prefs.getAll().isEmpty()) {
|
||||||
Log.i(TAG, "Device encrypted storage is empty, copying values from credential encrypted storage");
|
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;
|
return prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
public static Context getDeviceProtectedContext(final Context context) {
|
||||||
private static Context getDeviceProtectedContext(final Context context) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return context;
|
||||||
return context.isDeviceProtectedStorage()
|
return context.isDeviceProtectedStorage() ? context : context.createDeviceProtectedStorageContext();
|
||||||
? context : context.createDeviceProtectedStorageContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeviceProtectedUtils() {
|
private DeviceProtectedUtils() {
|
||||||
|
|
|
@ -11,7 +11,12 @@ import android.app.Application;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
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.BuildConfig;
|
||||||
|
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
@ -40,18 +45,28 @@ public final class JniUtils {
|
||||||
static {
|
static {
|
||||||
// hardcoded default path, may not work on all phones
|
// hardcoded default path, may not work on all phones
|
||||||
@SuppressLint("SdCardPath") String filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
@SuppressLint("SdCardPath") String filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
||||||
Application app = null;
|
Application app = App.Companion.getApp();
|
||||||
|
if (app == null) {
|
||||||
try {
|
try {
|
||||||
// try using reflection to get (app)context: https://stackoverflow.com/a/38967293
|
// 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")
|
app = (Application) Class.forName("android.app.ActivityThread")
|
||||||
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
||||||
// and use the actual path if possible
|
|
||||||
filesDir = app.getFilesDir().getAbsolutePath();
|
|
||||||
} catch (Exception ignored) { }
|
} catch (Exception ignored) { }
|
||||||
|
}
|
||||||
|
if (app != null) // use the actual path if possible
|
||||||
|
filesDir = app.getFilesDir().getAbsolutePath();
|
||||||
final File userSuppliedLibrary = new File(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
final File userSuppliedLibrary = new File(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
||||||
if (userSuppliedLibrary.exists()) {
|
if (userSuppliedLibrary.exists()) {
|
||||||
final String wantedChecksum = app == null ? expectedDefaultChecksum() : DeviceProtectedUtils.getSharedPreferences(app).getString("lib_checksum", "");
|
String wantedChecksum = expectedDefaultChecksum();
|
||||||
try {
|
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));
|
final String checksum = ChecksumCalculator.INSTANCE.checksum(new FileInputStream(userSuppliedLibrary));
|
||||||
if (TextUtils.equals(wantedChecksum, checksum)) {
|
if (TextUtils.equals(wantedChecksum, checksum)) {
|
||||||
// try loading the library
|
// try loading the library
|
||||||
|
@ -59,11 +74,9 @@ public final class JniUtils {
|
||||||
sHaveGestureLib = true; // this is an assumption, any way to actually check?
|
sHaveGestureLib = true; // this is an assumption, any way to actually check?
|
||||||
} else {
|
} else {
|
||||||
// delete if checksum doesn't match
|
// 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
|
// this 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();
|
||||||
// userSuppliedLibrary.delete();
|
sHaveGestureLib = false;
|
||||||
System.load(userSuppliedLibrary.getAbsolutePath());
|
|
||||||
sHaveGestureLib = true;
|
|
||||||
}
|
}
|
||||||
} catch (Throwable t) { // catch everything, maybe provided library simply doesn't work
|
} catch (Throwable t) { // catch everything, maybe provided library simply doesn't work
|
||||||
Log.w(TAG, "Could not load user-supplied library", t);
|
Log.w(TAG, "Could not load user-supplied library", t);
|
||||||
|
|
|
@ -154,6 +154,7 @@ fun getAvailableSubtypeLocales(): Collection<Locale> {
|
||||||
fun reloadEnabledSubtypes(context: Context) {
|
fun reloadEnabledSubtypes(context: Context) {
|
||||||
require(initialized)
|
require(initialized)
|
||||||
enabledSubtypes.clear()
|
enabledSubtypes.clear()
|
||||||
|
removeInvalidCustomSubtypes(context)
|
||||||
loadEnabledSubtypes(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
|
// remove custom subtypes without a layout file
|
||||||
private fun removeInvalidCustomSubtypes(context: Context) {
|
private fun removeInvalidCustomSubtypes(context: Context) {
|
||||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||||
val additionalSubtypes = Settings.readPrefAdditionalSubtypes(prefs, context.resources).split(";")
|
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>()
|
val subtypesToRemove = mutableListOf<String>()
|
||||||
additionalSubtypes.forEach {
|
additionalSubtypes.forEach {
|
||||||
val name = it.substringAfter(":").substringBefore(":")
|
val name = it.substringAfter(":").substringBefore(":")
|
||||||
|
@ -257,6 +251,13 @@ private fun removeInvalidCustomSubtypes(context: Context) {
|
||||||
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.filterNot { it in subtypesToRemove }.joinToString(";"))
|
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
|
// requires loadResourceSubtypes to be called before
|
||||||
private fun loadEnabledSubtypes(context: Context) {
|
private fun loadEnabledSubtypes(context: Context) {
|
||||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue