mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 07:22:45 +00:00
avoid loading uris from content resolver on main thread
because uris might point to network locations, which triggers an exception main thread is still blocked by loading, this is purely to get around the crash (files are small, and usually stored locally)
This commit is contained in:
parent
2dfc824319
commit
5e4361b119
6 changed files with 140 additions and 80 deletions
|
@ -6,12 +6,18 @@
|
|||
|
||||
package helium314.keyboard.latin.common;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils;
|
||||
|
||||
/**
|
||||
* A simple class to help with removing directories recursively.
|
||||
|
@ -47,7 +53,32 @@ public class FileUtils {
|
|||
return hasDeletedAllFiles;
|
||||
}
|
||||
|
||||
public static void copyStreamToNewFile(InputStream in, File outfile) throws IOException {
|
||||
/**
|
||||
* copy data to file on different thread to avoid NetworkOnMainThreadException
|
||||
* still effectively blocking, as we only use small files which are mostly stored locally
|
||||
*/
|
||||
public static void copyContentUriToNewFile(final Uri uri, final Context context, final File outfile) throws IOException {
|
||||
final boolean[] allOk = new boolean[] { true };
|
||||
final CountDownLatch wait = new CountDownLatch(1);
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> {
|
||||
try {
|
||||
copyStreamToNewFile(context.getContentResolver().openInputStream(uri), outfile);
|
||||
} catch (IOException e) {
|
||||
allOk[0] = false;
|
||||
} finally {
|
||||
wait.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
wait.await();
|
||||
} catch (InterruptedException e) {
|
||||
allOk[0] = false;
|
||||
}
|
||||
if (!allOk[0])
|
||||
throw new IOException("could not copy from uri");
|
||||
}
|
||||
|
||||
public static void copyStreamToNewFile(final InputStream in, final File outfile) throws IOException {
|
||||
File parentFile = outfile.getParentFile();
|
||||
if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) {
|
||||
throw new IOException("could not create parent folder");
|
||||
|
@ -57,7 +88,7 @@ public class FileUtils {
|
|||
out.close();
|
||||
}
|
||||
|
||||
public static void copyStreamToOtherStream(InputStream in, OutputStream out) throws IOException {
|
||||
public static void copyStreamToOtherStream(final InputStream in, final OutputStream out) throws IOException {
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.preference.Preference
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.Log
|
||||
import helium314.keyboard.latin.utils.SpannableStringUtils
|
||||
|
||||
|
@ -46,10 +47,12 @@ class AboutFragment : SubScreenFragment() {
|
|||
private val logFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = result.data?.data ?: return@registerForActivityResult
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(Log.getLog().joinToString("\n")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHiddenFeatures() {
|
||||
findPreference<Preference>("hidden_features")?.onPreferenceClickListener =
|
||||
|
|
|
@ -43,6 +43,7 @@ import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy
|
|||
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils
|
||||
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.JniUtils
|
||||
import helium314.keyboard.latin.utils.editCustomLayout
|
||||
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||
|
@ -54,6 +55,7 @@ import java.io.FileInputStream
|
|||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
@ -207,12 +209,15 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
private fun copyLibrary(uri: Uri) {
|
||||
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
||||
try {
|
||||
val inputStream = requireContext().contentResolver.openInputStream(uri)
|
||||
val otherTemporaryFile = File(requireContext().filesDir.absolutePath + File.separator + "tmpfile")
|
||||
FileUtils.copyContentUriToNewFile(uri, requireContext(), otherTemporaryFile)
|
||||
val inputStream = FileInputStream(otherTemporaryFile)
|
||||
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)
|
||||
}
|
||||
otherTemporaryFile.delete()
|
||||
|
||||
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
|
||||
if (checksum == JniUtils.expectedDefaultChecksum()) {
|
||||
|
@ -269,7 +274,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
|
||||
private fun loadImage(uri: Uri, night: Boolean) {
|
||||
val imageFile = Settings.getCustomBackgroundFile(requireContext(), night)
|
||||
FileUtils.copyStreamToNewFile(requireContext().contentResolver.openInputStream(uri), imageFile)
|
||||
FileUtils.copyContentUriToNewFile(uri, requireContext(), imageFile)
|
||||
try {
|
||||
BitmapFactory.decodeFile(imageFile.absolutePath)
|
||||
} catch (_: Exception) {
|
||||
|
@ -334,6 +339,9 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
if (backupFilePatterns.any { path.matches(it) })
|
||||
protectedFiles.add(file)
|
||||
}
|
||||
var error: String? = ""
|
||||
val wait = CountDownLatch(1)
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
try {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
// write files to zip
|
||||
|
@ -361,18 +369,28 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
zipStream.close()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
// inform about every error
|
||||
error = t.message
|
||||
Log.w(TAG, "error during backup", t)
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, t.message))
|
||||
} finally {
|
||||
wait.countDown()
|
||||
}
|
||||
}
|
||||
wait.await()
|
||||
if (!error.isNullOrBlank()) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun restore(uri: Uri) {
|
||||
var error: String? = ""
|
||||
val wait = CountDownLatch(1)
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
try {
|
||||
activity?.contentResolver?.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zip ->
|
||||
var entry: ZipEntry? = zip.nextEntry
|
||||
val filesDir = requireContext().filesDir?.path ?: return
|
||||
val filesDir = requireContext().filesDir?.path ?: return@execute
|
||||
val deviceProtectedFilesDir = DeviceProtectedUtils.getFilesDir(requireContext()).path
|
||||
Settings.getInstance().stopListener()
|
||||
while (entry != null) {
|
||||
|
@ -403,10 +421,17 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
// inform about every error
|
||||
error = t.message
|
||||
Log.w(TAG, "error during restore", t)
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, t.message))
|
||||
} finally {
|
||||
wait.countDown()
|
||||
}
|
||||
}
|
||||
wait.await()
|
||||
if (!error.isNullOrBlank()) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, error))
|
||||
}
|
||||
checkVersionUpgrade(requireContext())
|
||||
Settings.getInstance().startListener()
|
||||
val additionalSubtypes = Settings.readPrefAdditionalSubtypes(sharedPreferences, resources);
|
||||
|
@ -419,7 +444,6 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
setupPreferences()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
// todo (later): remove this when new package name has been in use for long enough, this is only for migrating from old openboard name
|
||||
private fun upgradeFileNames(originalName: String): String {
|
||||
|
|
|
@ -128,7 +128,7 @@ public final class SettingsFragment extends PreferenceFragmentCompat {
|
|||
if (intent.getResultCode() != Activity.RESULT_OK || intent.getData() == null) return;
|
||||
final Uri uri = intent.getData().getData();
|
||||
if (uri != null)
|
||||
saveCrashReport(uri);
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> saveCrashReport(uri));
|
||||
});
|
||||
|
||||
private void saveCrashReport(final Uri uri) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.SimpleKeyboardParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -28,8 +29,10 @@ fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded:
|
|||
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
|
||||
val layoutContent: String
|
||||
try {
|
||||
val i = context.contentResolver.openInputStream(uri)
|
||||
layoutContent = i?.use { it.reader().readText() } ?: throw IOException()
|
||||
val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile")
|
||||
FileUtils.copyContentUriToNewFile(uri, context, tmpFile)
|
||||
layoutContent = tmpFile.readText()
|
||||
tmpFile.delete()
|
||||
} catch (e: IOException) {
|
||||
return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file"))
|
||||
}
|
||||
|
|
|
@ -30,8 +30,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo
|
|||
|
||||
cachedDictionaryFile.delete()
|
||||
try {
|
||||
val i = context.contentResolver.openInputStream(uri)
|
||||
FileUtils.copyStreamToNewFile(i, cachedDictionaryFile)
|
||||
FileUtils.copyContentUriToNewFile(uri, context, cachedDictionaryFile)
|
||||
} catch (e: IOException) {
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue