diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt index d1cc42298..ec870c9dd 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt @@ -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>) { 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> { 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")) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/App.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/App.kt index 1ed1b5009..7fec1f27e 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/App.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/App.kt @@ -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() + 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) } } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt index fe78def0d..4db2f5f3a 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ClipboardHistoryManager.kt @@ -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 = 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 { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt index 958d80853..3a9075cd5 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt @@ -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("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("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() + 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" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java index f3b8e42fb..a0af1fbdf 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java @@ -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) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt index b01c05247..76fb5cbda 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt @@ -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>): 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()) } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DeviceProtectedUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DeviceProtectedUtils.java index 0f4c4c783..9b55cd633 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DeviceProtectedUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DeviceProtectedUtils.java @@ -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() { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JniUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JniUtils.java index 20a6c82e6..3b8b7bb45 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JniUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/JniUtils.java @@ -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); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt index 8466397d5..576ea878c 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt @@ -154,6 +154,7 @@ fun getAvailableSubtypeLocales(): Collection { 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() 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)