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:
Helium314 2024-01-28 18:05:38 +01:00
parent b714fc6566
commit 89afcfd9d3
9 changed files with 179 additions and 72 deletions

View file

@ -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")) {

View file

@ -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) }
}

View file

@ -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 {

View file

@ -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"

View file

@ -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) {

View file

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

View file

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

View file

@ -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);

View file

@ -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)