mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-21 22:59:10 +00:00
basic checks for supplied gesture library
This commit is contained in:
parent
7c5b96a034
commit
d6769f6d65
5 changed files with 80 additions and 21 deletions
|
@ -18,6 +18,7 @@ import androidx.preference.Preference
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.ChecksumCalculator
|
||||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
|
||||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
|
||||||
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager
|
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager
|
||||||
|
@ -30,6 +31,7 @@ import org.dslul.openboard.inputmethod.latin.utils.JniUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
|
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.Writer
|
import java.io.Writer
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
|
@ -49,7 +51,7 @@ import java.util.zip.ZipOutputStream
|
||||||
* - Debug settings
|
* - Debug settings
|
||||||
*/
|
*/
|
||||||
class AdvancedSettingsFragment : SubScreenFragment() {
|
class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
private var libfile: File? = null
|
private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) }
|
||||||
private val backupFilePatterns by lazy { listOf(
|
private val backupFilePatterns by lazy { listOf(
|
||||||
"blacklists/.*\\.txt".toRegex(),
|
"blacklists/.*\\.txt".toRegex(),
|
||||||
"layouts/.*.(txt|json)".toRegex(),
|
"layouts/.*.(txt|json)".toRegex(),
|
||||||
|
@ -119,10 +121,9 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
libraryFilePicker.launch(intent)
|
libraryFilePicker.launch(intent)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
libfile = File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME)
|
if (libfile.exists()) {
|
||||||
if (libfile?.exists() == true) {
|
|
||||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||||
libfile?.delete()
|
libfile.delete()
|
||||||
Runtime.getRuntime().exit(0)
|
Runtime.getRuntime().exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,16 +132,44 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyLibrary(uri: Uri) {
|
private fun copyLibrary(uri: Uri) {
|
||||||
if (libfile == null) return
|
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
||||||
try {
|
try {
|
||||||
val inputStream = requireContext().contentResolver.openInputStream(uri)
|
val inputStream = requireContext().contentResolver.openInputStream(uri)
|
||||||
FileUtils.copyStreamToNewFile(inputStream, libfile)
|
val outputStream = FileOutputStream(tmpfile)
|
||||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
outputStream.use {
|
||||||
|
tmpfile.setReadOnly() // as per recommendations in https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
|
||||||
|
FileUtils.copyStreamToOtherStream(inputStream, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
|
||||||
|
Log.i("test", "cs $checksum")
|
||||||
|
if (checksum == JniUtils.expectedDefaultChecksum()) {
|
||||||
|
renameToLibfileAndRestart(tmpfile, checksum)
|
||||||
|
} else {
|
||||||
|
val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Build.SUPPORTED_ABIS[0]
|
||||||
|
} else {
|
||||||
|
@Suppress("Deprecation") Build.CPU_ABI
|
||||||
|
}
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setMessage(getString(R.string.checksum_mismatch_message, abi))
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ -> renameToLibfileAndRestart(tmpfile, checksum) }
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> tmpfile.delete() }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
tmpfile.delete()
|
||||||
// should inform user, but probably the issues will only come when reading the library
|
// should inform user, but probably the issues will only come when reading the library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renameToLibfileAndRestart(file: File, checksum: String) {
|
||||||
|
libfile.delete()
|
||||||
|
sharedPreferences.edit().putString("lib_checksum", checksum).commit()
|
||||||
|
file.renameTo(libfile)
|
||||||
|
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
||||||
|
}
|
||||||
|
|
||||||
private fun showBackupRestoreDialog(): Boolean {
|
private fun showBackupRestoreDialog(): Boolean {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.backup_restore_title)
|
.setTitle(R.string.backup_restore_title)
|
||||||
|
|
|
@ -4,20 +4,20 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.dslul.openboard.inputmethod.dictionarypack
|
package org.dslul.openboard.inputmethod.latin.utils
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
object MD5Calculator {
|
object ChecksumCalculator {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun checksum(`in`: InputStream): String? { // This code from the Android documentation for MessageDigest. Nearly verbatim.
|
fun checksum(`in`: InputStream): String? { // This code from the Android documentation for MessageDigest. Nearly verbatim.
|
||||||
val digester: MessageDigest = try {
|
val digester: MessageDigest = try {
|
||||||
MessageDigest.getInstance("MD5")
|
MessageDigest.getInstance("SHA-256")
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
return null // Platform does not support MD5 : can't check, so return null
|
return null // Platform does not support SHA-256 : can't check, so return null
|
||||||
}
|
}
|
||||||
val bytes = ByteArray(8192)
|
val bytes = ByteArray(8192)
|
||||||
var byteCount: Int
|
var byteCount: Int
|
|
@ -10,7 +10,6 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
|
|
@ -6,42 +6,71 @@
|
||||||
|
|
||||||
package org.dslul.openboard.inputmethod.latin.utils;
|
package org.dslul.openboard.inputmethod.latin.utils;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
public final class JniUtils {
|
public final class JniUtils {
|
||||||
private static final String TAG = JniUtils.class.getSimpleName();
|
private static final String TAG = JniUtils.class.getSimpleName();
|
||||||
public static final String JNI_LIB_NAME = "jni_latinime";
|
public static final String JNI_LIB_NAME = "jni_latinime";
|
||||||
public static final String JNI_LIB_NAME_GOOGLE = "jni_latinimegoogle";
|
public static final String JNI_LIB_NAME_GOOGLE = "jni_latinimegoogle";
|
||||||
public static final String JNI_LIB_IMPORT_FILE_NAME = "libjni_latinime.so";
|
public static final String JNI_LIB_IMPORT_FILE_NAME = "libjni_latinime.so";
|
||||||
|
private static final String CHECKSUM_ARM64 = "b1049983e6ac5cfc6d1c66e38959751044fad213dff0637a6cf1d2a2703e754f";
|
||||||
|
private static final String CHECKSUM_ARM32 = "442a2a8bfcb25489564bc9433a916fa4dc0dba9000fe6f6f03f5939b985091e6";
|
||||||
|
private static final String CHECKSUM_X86_64 = "c882e12e6d48dd946e0b644c66868a720bd11ac3fecf152000e21a3d5abd59c9";
|
||||||
|
private static final String CHECKSUM_X86 = "bd946d126c957b5a6dea3bafa07fa36a27950b30e2b684dffc60746d0a1c7ad8";
|
||||||
|
|
||||||
|
public static String expectedDefaultChecksum() {
|
||||||
|
final String abi = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_ABIS[0] : Build.CPU_ABI;
|
||||||
|
return switch (abi) {
|
||||||
|
case "arm64-v8a" -> CHECKSUM_ARM64;
|
||||||
|
case "armeabi-v7a" -> CHECKSUM_ARM32;
|
||||||
|
case "x86_64" -> CHECKSUM_X86_64;
|
||||||
|
case "x86" -> CHECKSUM_X86;
|
||||||
|
default -> "-"; // invalid checksum that definitely will not match
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean sHaveGestureLib = false;
|
public static boolean sHaveGestureLib = false;
|
||||||
static {
|
static {
|
||||||
String filesDir;
|
// hardcoded default path, may not work on all phones
|
||||||
|
@SuppressLint("SdCardPath") String filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
||||||
|
Application app = null;
|
||||||
try {
|
try {
|
||||||
// try using reflection to get (app)context: https://stackoverflow.com/a/38967293
|
// try using reflection to get (app)context: https://stackoverflow.com/a/38967293
|
||||||
final Application app = (Application) Class.forName("android.app.ActivityThread")
|
app = (Application) Class.forName("android.app.ActivityThread")
|
||||||
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
.getMethod("currentApplication").invoke(null, (Object[]) null);
|
||||||
|
// and use the actual path if possible
|
||||||
filesDir = app.getFilesDir().getAbsolutePath();
|
filesDir = app.getFilesDir().getAbsolutePath();
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) { }
|
||||||
// fall back to hardcoded default path, may not work on all phones
|
|
||||||
filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
|
||||||
}
|
|
||||||
final File userSuppliedLibrary = new File(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
final File userSuppliedLibrary = new File(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
||||||
if (userSuppliedLibrary.exists()) {
|
if (userSuppliedLibrary.exists()) {
|
||||||
|
final String wantedChecksum = app == null ? expectedDefaultChecksum() : DeviceProtectedUtils.getSharedPreferences(app).getString("lib_checksum", "");
|
||||||
try {
|
try {
|
||||||
System.load(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
final String checksum = ChecksumCalculator.INSTANCE.checksum(new FileInputStream(userSuppliedLibrary));
|
||||||
sHaveGestureLib = true; // this is an assumption, any way to actually check?
|
if (TextUtils.equals(wantedChecksum, checksum)) {
|
||||||
|
// try loading the library
|
||||||
|
System.load(userSuppliedLibrary.getAbsolutePath());
|
||||||
|
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: this is disabled until app is renamed, otherwise it will delete everyone's library!
|
||||||
|
// though there could be a default check?
|
||||||
|
// userSuppliedLibrary.delete();
|
||||||
|
}
|
||||||
} catch (Throwable t) { // catch everything, maybe provided library simply doesn't work
|
} catch (Throwable t) { // catch everything, maybe provided library simply doesn't work
|
||||||
Log.w(TAG, "Could not load user-supplied library", t);
|
Log.w(TAG, "Could not load user-supplied library", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sHaveGestureLib) {
|
if (!sHaveGestureLib) {
|
||||||
// try loading google library, will fail unless it's in system and this is a system app
|
// try loading google library, will fail unless the library is in system and this is a system app
|
||||||
try {
|
try {
|
||||||
System.loadLibrary(JNI_LIB_NAME_GOOGLE);
|
System.loadLibrary(JNI_LIB_NAME_GOOGLE);
|
||||||
sHaveGestureLib = true;
|
sHaveGestureLib = true;
|
||||||
|
|
|
@ -177,6 +177,8 @@
|
||||||
<!-- Message in load gesture library dialog -->
|
<!-- Message in load gesture library dialog -->
|
||||||
<string name="load_gesture_library_message">You will need the library for \'%s\'. Incompatible libraries may crash when using gesture typing.
|
<string name="load_gesture_library_message">You will need the library for \'%s\'. Incompatible libraries may crash when using gesture typing.
|
||||||
\n\nWarning: loading external code can be a security risk. Only use a library from a source you trust.</string>
|
\n\nWarning: loading external code can be a security risk. Only use a library from a source you trust.</string>
|
||||||
|
<!-- Message when the library checksum doesn't match -->
|
||||||
|
<string name="checksum_mismatch_message">Unknown library file. Are you sure you got it from a trusted source and it is for \'%s\'?</string>
|
||||||
<!-- Button text for loading gesture library -->
|
<!-- Button text for loading gesture library -->
|
||||||
<string name="load_gesture_library_button_load">Load library</string>
|
<string name="load_gesture_library_button_load">Load library</string>
|
||||||
<!-- Button text for deleting gesture library -->
|
<!-- Button text for deleting gesture library -->
|
||||||
|
|
Loading…
Add table
Reference in a new issue