diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d7fe7a2..0b6f195b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,10 +84,11 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). - Functions in the application have been documented ([#18](https://github.com/scribe-org/Scribe-Android/issues/18), [#354](https://github.com/scribe-org/Scribe-Android/issues/354)). -### Bug Fixes +### 🐛 Bug Fixes - The keyboard service no longer crashes when an editor starts input with no `EditorInfo` ([#699](https://github.com/scribe-org/Scribe-Android/issues/699)). - Downloading or checking for language data updates no longer crashes the app when the server returns a malformed timestamp ([#700](https://github.com/scribe-org/Scribe-Android/issues/700)). +- Native dictionaries are re-extracted after an app update so autosuggestions no longer use dictionaries from a previous install ([#701](https://github.com/scribe-org/Scribe-Android/issues/701)). ### 🔒 Security diff --git a/app/src/main/java/be/scri/helpers/NativeSuggestionEngine.kt b/app/src/main/java/be/scri/helpers/NativeSuggestionEngine.kt index 0059f8e2..6154b92c 100644 --- a/app/src/main/java/be/scri/helpers/NativeSuggestionEngine.kt +++ b/app/src/main/java/be/scri/helpers/NativeSuggestionEngine.kt @@ -4,6 +4,8 @@ package be.scri.helpers import android.content.Context +import android.content.pm.PackageManager +import android.os.Build import android.util.Log import be.scri.inputmethod.keyboard.ProximityInfo import be.scri.latin.NgramContext @@ -24,6 +26,7 @@ class NativeSuggestionEngine(private val context: Context) { companion object { private const val TAG = "NativeSuggestionEngine" private const val DICT_DIR = "dicts" + private const val DICT_PREFS = "native_dict_prefs" } private val loadedDicts = HashMap() @@ -57,24 +60,49 @@ class NativeSuggestionEngine(private val context: Context) { } val targetFile = File(dictsFolder, assetName) - if (targetFile.exists() && targetFile.length() > 0) { - return targetFile + val existingFile = targetFile.takeIf { it.exists() && it.length() > 0 } + val prefs = context.getSharedPreferences(DICT_PREFS, Context.MODE_PRIVATE) + val appUpdateTime = getAppLastUpdateTime() + if (existingFile != null && prefs.getLong(assetName, -1L) == appUpdateTime) { + return existingFile } + val tempFile = File(dictsFolder, "$assetName.tmp") try { context.assets.open("dicts/$assetName").use { inputStream -> - FileOutputStream(targetFile).use { outputStream -> + FileOutputStream(tempFile).use { outputStream -> inputStream.copyTo(outputStream) } } + if (!tempFile.renameTo(targetFile)) { + Log.e(TAG, "Failed to move extracted native dictionary into place: $assetName") + tempFile.delete() + return existingFile + } + prefs.edit().putLong(assetName, appUpdateTime).apply() Log.i(TAG, "Successfully extracted native dictionary: $assetName") return targetFile } catch (e: IOException) { + tempFile.delete() Log.e(TAG, "Error extracting native dictionary $assetName from assets", e) - return null + return existingFile } } + private fun getAppLastUpdateTime(): Long = + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager + .getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0)) + .lastUpdateTime + } else { + @Suppress("DEPRECATION") + context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime + } + } catch (e: PackageManager.NameNotFoundException) { + 0L + } + /** * Retrieves or loads the BinaryDictionary for the given language. */