mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-20 14:19:08 +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.json.Json
|
||||
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.KeyboardSwitcher
|
||||
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 java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
import java.util.zip.ZipEntry
|
||||
|
@ -49,7 +51,7 @@ import java.util.zip.ZipOutputStream
|
|||
* - Debug settings
|
||||
*/
|
||||
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(
|
||||
"blacklists/.*\\.txt".toRegex(),
|
||||
"layouts/.*.(txt|json)".toRegex(),
|
||||
|
@ -119,10 +121,9 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
libraryFilePicker.launch(intent)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
libfile = File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME)
|
||||
if (libfile?.exists() == true) {
|
||||
if (libfile.exists()) {
|
||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||
libfile?.delete()
|
||||
libfile.delete()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
|
@ -131,16 +132,44 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
}
|
||||
|
||||
private fun copyLibrary(uri: Uri) {
|
||||
if (libfile == null) return
|
||||
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
||||
try {
|
||||
val inputStream = requireContext().contentResolver.openInputStream(uri)
|
||||
FileUtils.copyStreamToNewFile(inputStream, libfile)
|
||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
||||
val outputStream = FileOutputStream(tmpfile)
|
||||
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) {
|
||||
tmpfile.delete()
|
||||
// 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 {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.backup_restore_title)
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
* 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.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
object MD5Calculator {
|
||||
object ChecksumCalculator {
|
||||
@Throws(IOException::class)
|
||||
fun checksum(`in`: InputStream): String? { // This code from the Android documentation for MessageDigest. Nearly verbatim.
|
||||
val digester: MessageDigest = try {
|
||||
MessageDigest.getInstance("MD5")
|
||||
MessageDigest.getInstance("SHA-256")
|
||||
} 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)
|
||||
var byteCount: Int
|
|
@ -10,7 +10,6 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
|
|
|
@ -6,42 +6,71 @@
|
|||
|
||||
package org.dslul.openboard.inputmethod.latin.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
public final class JniUtils {
|
||||
private static final String TAG = JniUtils.class.getSimpleName();
|
||||
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_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;
|
||||
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 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);
|
||||
// and use the actual path if possible
|
||||
filesDir = app.getFilesDir().getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
// fall back to hardcoded default path, may not work on all phones
|
||||
filesDir = "/data/data/" + BuildConfig.APPLICATION_ID + "/files";
|
||||
}
|
||||
} 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", "");
|
||||
try {
|
||||
System.load(filesDir + File.separator + JNI_LIB_IMPORT_FILE_NAME);
|
||||
sHaveGestureLib = true; // this is an assumption, any way to actually check?
|
||||
final String checksum = ChecksumCalculator.INSTANCE.checksum(new FileInputStream(userSuppliedLibrary));
|
||||
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
|
||||
Log.w(TAG, "Could not load user-supplied library", t);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
System.loadLibrary(JNI_LIB_NAME_GOOGLE);
|
||||
sHaveGestureLib = true;
|
||||
|
|
|
@ -177,6 +177,8 @@
|
|||
<!-- 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.
|
||||
\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 -->
|
||||
<string name="load_gesture_library_button_load">Load library</string>
|
||||
<!-- Button text for deleting gesture library -->
|
||||
|
|
Loading…
Add table
Reference in a new issue