Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
36 changes: 32 additions & 4 deletions app/src/main/java/be/scri/helpers/NativeSuggestionEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String, ReadOnlyBinaryDictionary>()
Expand Down Expand Up @@ -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.
*/
Expand Down
Loading