HeliBoard/app/src/main/java/helium314/keyboard/latin/App.kt

245 lines
12 KiB
Kotlin

// SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
package helium314.keyboard.latin
import android.app.Application
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ToolbarKey
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
import helium314.keyboard.latin.utils.getCustomLayoutFile
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
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)
if (oldVersion == BuildConfig.VERSION_CODE)
return
// clear extracted dictionaries, in case updated version contains newer ones
DictionaryInfoUtils.getCachedDirectoryList(context)?.forEach {
if (!it.isDirectory) return@forEach
val files = it.listFiles() ?: return@forEach
for (file in files) {
if (!file.name.endsWith(USER_DICTIONARY_SUFFIX))
file.delete()
}
}
if (oldVersion == 0) // new install or restoring settings from old app name
upgradesWhenComingFromOldAppName(context)
if (oldVersion <= 1000) { // upgrade old custom layouts name
val oldShiftSymbolsFile = getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}shift_symbols", context)
if (oldShiftSymbolsFile.exists()) {
oldShiftSymbolsFile.renameTo(getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}symbols_shifted", context))
}
// rename subtype setting, and clean old subtypes that might remain in some cases
val subtypesPref = prefs.getString("enabled_input_styles", "")!!
.split(";").filter { it.isNotEmpty() }
.map {
val localeAndLayout = it.split(":").toMutableList()
localeAndLayout[0] = localeAndLayout[0].constructLocale().toLanguageTag()
localeAndLayout.joinToString(":")
}.toSet().joinToString(";")
val selectedSubtype = prefs.getString("selected_input_style", "")
prefs.edit {
remove("enabled_input_styles")
putString(Settings.PREF_ENABLED_SUBTYPES, subtypesPref)
remove("selected_input_style")
putString(Settings.PREF_SELECTED_SUBTYPE, selectedSubtype)
}
}
if (oldVersion <= 2000) {
// upgrade pinned toolbar keys pref
val oldPinnedKeysPref = prefs.getString(Settings.PREF_PINNED_TOOLBAR_KEYS, "")!!
val pinnedKeys = oldPinnedKeysPref.split(";").mapNotNull {
try {
ToolbarKey.valueOf(it)
} catch (_: IllegalArgumentException) {
null
}
}
val newPinnedKeysPref = (pinnedKeys.map { "${it.name},true" } + defaultPinnedToolbarPref.split(";"))
.distinctBy { it.split(",").first() }
.joinToString(";")
prefs.edit { putString(Settings.PREF_PINNED_TOOLBAR_KEYS, newPinnedKeysPref) }
// enable language switch key if it was enabled previously
if (prefs.contains(Settings.PREF_LANGUAGE_SWITCH_KEY) && prefs.getString(Settings.PREF_LANGUAGE_SWITCH_KEY, "") != "off")
prefs.edit { putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true) }
}
if (oldVersion <= 2100) {
if (prefs.contains(Settings.PREF_SHOW_MORE_COLORS)) {
val moreColors = prefs.getInt(Settings.PREF_SHOW_MORE_COLORS, 0)
prefs.edit {
putInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, false), moreColors)
if (prefs.getBoolean(Settings.PREF_THEME_DAY_NIGHT, false))
putInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, true), moreColors)
remove(Settings.PREF_SHOW_MORE_COLORS)
}
}
}
if (oldVersion <= 2201) {
val additionalSubtypeString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
if (additionalSubtypeString.contains(".")) { // means there are custom layouts
val subtypeStrings = additionalSubtypeString.split(";")
val newSubtypeStrings = subtypeStrings.mapNotNull {
val split = it.split(":").toMutableList()
Log.i("test", "0: $it")
if (split.size < 2) return@mapNotNull null // should never happen
val oldName = split[1]
val newName = oldName.substringBeforeLast(".") + "."
if (oldName == newName) return@mapNotNull split.joinToString(":") // should never happen
val oldFile = getCustomLayoutFile(oldName, context)
val newFile = getCustomLayoutFile(newName, context)
Log.i("test", "1")
if (!oldFile.exists()) return@mapNotNull null // should never happen
Log.i("test", "2")
if (newFile.exists()) newFile.delete() // should never happen
Log.i("test", "3")
oldFile.renameTo(newFile)
val enabledSubtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, "")!!
if (enabledSubtypes.contains(oldName))
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, enabledSubtypes.replace(oldName, newName)) }
val selectedSubtype = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, "")!!
if (selectedSubtype.contains(oldName))
prefs.edit { putString(Settings.PREF_SELECTED_SUBTYPE, selectedSubtype.replace(oldName, newName)) }
split[1] = newName
split.joinToString(":")
}
Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";"))
}
// rename other custom layouts
onCustomLayoutFileListChanged()
getCustomLayoutFiles(context).forEach {
val newFile = getCustomLayoutFile(it.name.substringBeforeLast(".") + ".", context)
if (newFile.name == it.name) return@forEach
if (newFile.exists()) newFile.delete() // should never happen
it.renameTo(newFile)
}
}
upgradeToolbarPrefs(prefs)
onCustomLayoutFileListChanged() // just to be sure
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
}
// todo (later): remove it when most users probably have upgraded
private fun upgradesWhenComingFromOldAppName(context: Context) {
// move layout files
try {
File(context.filesDir, "layouts").listFiles()?.forEach {
it.copyTo(getCustomLayoutFile(it.name, context), 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, false), true)
bgDay.delete()
}
val bgNight = File(context.filesDir, "custom_background_image_night")
if (bgNight.isFile) {
bgNight.copyTo(Settings.getCustomBackgroundFile(context, true, false), 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()
}
if (prefs.all.containsKey("theme_variant_night")) {
prefs.edit().putString(Settings.PREF_THEME_COLORS_NIGHT, prefs.getString("theme_variant_night", "")).apply()
prefs.edit().remove("theme_variant_night").apply()
}
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()
}
}
// change more_keys to popup_keys
if (prefs.contains("more_keys_order")) {
prefs.edit().putString(Settings.PREF_POPUP_KEYS_ORDER, prefs.getString("more_keys_order", "")?.replace("more_", "popup_")).apply()
prefs.edit().remove("more_keys_order").apply()
}
if (prefs.contains("more_keys_labels_order")) {
prefs.edit().putString(Settings.PREF_POPUP_KEYS_LABELS_ORDER, prefs.getString("more_keys_labels_order", "")?.replace("more_", "popup_")).apply()
prefs.edit().remove("more_keys_labels_order").apply()
}
if (prefs.contains("more_more_keys")) {
prefs.edit().putString(Settings.PREF_MORE_POPUP_KEYS, prefs.getString("more_more_keys", "")).apply()
prefs.edit().remove("more_more_keys").apply()
}
if (prefs.contains("spellcheck_use_contacts")) {
prefs.edit().putBoolean(Settings.PREF_USE_CONTACTS, prefs.getBoolean("spellcheck_use_contacts", false)).apply()
prefs.edit().remove("spellcheck_use_contacts").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 device is not locked (should never happen)
if (!prefs.contains(Settings.PREF_PINNED_CLIPS)) return
try {
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) }
} catch (_: IllegalStateException) {
// SharedPreferences in credential encrypted storage are not available until after user is unlocked
}
}