mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 15:32:48 +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;
|
package helium314.keyboard.latin.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import helium314.keyboard.latin.utils.ExecutorUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple class to help with removing directories recursively.
|
* A simple class to help with removing directories recursively.
|
||||||
|
@ -47,7 +53,32 @@ public class FileUtils {
|
||||||
return hasDeletedAllFiles;
|
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();
|
File parentFile = outfile.getParentFile();
|
||||||
if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) {
|
if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) {
|
||||||
throw new IOException("could not create parent folder");
|
throw new IOException("could not create parent folder");
|
||||||
|
@ -57,7 +88,7 @@ public class FileUtils {
|
||||||
out.close();
|
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];
|
byte[] buf = new byte[1024];
|
||||||
int len;
|
int len;
|
||||||
while ((len = in.read(buf)) > 0) {
|
while ((len = in.read(buf)) > 0) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import helium314.keyboard.latin.BuildConfig
|
import helium314.keyboard.latin.BuildConfig
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||||
import helium314.keyboard.latin.utils.Log
|
import helium314.keyboard.latin.utils.Log
|
||||||
import helium314.keyboard.latin.utils.SpannableStringUtils
|
import helium314.keyboard.latin.utils.SpannableStringUtils
|
||||||
|
|
||||||
|
@ -46,8 +47,10 @@ class AboutFragment : SubScreenFragment() {
|
||||||
private val logFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val logFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val uri = result.data?.data ?: return@registerForActivityResult
|
val uri = result.data?.data ?: return@registerForActivityResult
|
||||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||||
os.bufferedWriter().use { it.write(Log.getLog().joinToString("\n")) }
|
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||||
|
os.bufferedWriter().use { it.write(Log.getLog().joinToString("\n")) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy
|
||||||
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils
|
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
||||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
||||||
|
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||||
import helium314.keyboard.latin.utils.JniUtils
|
import helium314.keyboard.latin.utils.JniUtils
|
||||||
import helium314.keyboard.latin.utils.editCustomLayout
|
import helium314.keyboard.latin.utils.editCustomLayout
|
||||||
import helium314.keyboard.latin.utils.getStringResourceOrName
|
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||||
|
@ -54,6 +55,7 @@ import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
@ -207,12 +209,15 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
private fun copyLibrary(uri: Uri) {
|
private fun copyLibrary(uri: Uri) {
|
||||||
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
||||||
try {
|
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)
|
val outputStream = FileOutputStream(tmpfile)
|
||||||
outputStream.use {
|
outputStream.use {
|
||||||
tmpfile.setReadOnly() // as per recommendations in https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
|
tmpfile.setReadOnly() // as per recommendations in https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
|
||||||
FileUtils.copyStreamToOtherStream(inputStream, it)
|
FileUtils.copyStreamToOtherStream(inputStream, it)
|
||||||
}
|
}
|
||||||
|
otherTemporaryFile.delete()
|
||||||
|
|
||||||
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
|
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
|
||||||
if (checksum == JniUtils.expectedDefaultChecksum()) {
|
if (checksum == JniUtils.expectedDefaultChecksum()) {
|
||||||
|
@ -269,7 +274,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
|
|
||||||
private fun loadImage(uri: Uri, night: Boolean) {
|
private fun loadImage(uri: Uri, night: Boolean) {
|
||||||
val imageFile = Settings.getCustomBackgroundFile(requireContext(), night)
|
val imageFile = Settings.getCustomBackgroundFile(requireContext(), night)
|
||||||
FileUtils.copyStreamToNewFile(requireContext().contentResolver.openInputStream(uri), imageFile)
|
FileUtils.copyContentUriToNewFile(uri, requireContext(), imageFile)
|
||||||
try {
|
try {
|
||||||
BitmapFactory.decodeFile(imageFile.absolutePath)
|
BitmapFactory.decodeFile(imageFile.absolutePath)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
|
@ -334,91 +339,110 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
if (backupFilePatterns.any { path.matches(it) })
|
if (backupFilePatterns.any { path.matches(it) })
|
||||||
protectedFiles.add(file)
|
protectedFiles.add(file)
|
||||||
}
|
}
|
||||||
try {
|
var error: String? = ""
|
||||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
val wait = CountDownLatch(1)
|
||||||
// write files to zip
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||||
val zipStream = ZipOutputStream(os)
|
try {
|
||||||
files.forEach {
|
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||||
val fileStream = FileInputStream(it).buffered()
|
// write files to zip
|
||||||
zipStream.putNextEntry(ZipEntry(it.path.replace(filesPath, "")))
|
val zipStream = ZipOutputStream(os)
|
||||||
fileStream.copyTo(zipStream, 1024)
|
files.forEach {
|
||||||
fileStream.close()
|
val fileStream = FileInputStream(it).buffered()
|
||||||
|
zipStream.putNextEntry(ZipEntry(it.path.replace(filesPath, "")))
|
||||||
|
fileStream.copyTo(zipStream, 1024)
|
||||||
|
fileStream.close()
|
||||||
|
zipStream.closeEntry()
|
||||||
|
}
|
||||||
|
protectedFiles.forEach {
|
||||||
|
val fileStream = FileInputStream(it).buffered()
|
||||||
|
zipStream.putNextEntry(ZipEntry(it.path.replace(protectedFilesDir.path, "unprotected")))
|
||||||
|
fileStream.copyTo(zipStream, 1024)
|
||||||
|
fileStream.close()
|
||||||
|
zipStream.closeEntry()
|
||||||
|
}
|
||||||
|
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
||||||
|
settingsToJsonStream(sharedPreferences.all, zipStream)
|
||||||
zipStream.closeEntry()
|
zipStream.closeEntry()
|
||||||
}
|
zipStream.putNextEntry(ZipEntry(PROTECTED_PREFS_FILE_NAME))
|
||||||
protectedFiles.forEach {
|
settingsToJsonStream(PreferenceManager.getDefaultSharedPreferences(requireContext()).all, zipStream)
|
||||||
val fileStream = FileInputStream(it).buffered()
|
|
||||||
zipStream.putNextEntry(ZipEntry(it.path.replace(protectedFilesDir.path, "unprotected")))
|
|
||||||
fileStream.copyTo(zipStream, 1024)
|
|
||||||
fileStream.close()
|
|
||||||
zipStream.closeEntry()
|
zipStream.closeEntry()
|
||||||
|
zipStream.close()
|
||||||
}
|
}
|
||||||
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
} catch (t: Throwable) {
|
||||||
settingsToJsonStream(sharedPreferences.all, zipStream)
|
error = t.message
|
||||||
zipStream.closeEntry()
|
Log.w(TAG, "error during backup", t)
|
||||||
zipStream.putNextEntry(ZipEntry(PROTECTED_PREFS_FILE_NAME))
|
} finally {
|
||||||
settingsToJsonStream(PreferenceManager.getDefaultSharedPreferences(requireContext()).all, zipStream)
|
wait.countDown()
|
||||||
zipStream.closeEntry()
|
|
||||||
zipStream.close()
|
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
}
|
||||||
|
wait.await()
|
||||||
|
if (!error.isNullOrBlank()) {
|
||||||
// inform about every error
|
// inform about every error
|
||||||
Log.w(TAG, "error during backup", t)
|
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, error))
|
||||||
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, t.message))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restore(uri: Uri) {
|
private fun restore(uri: Uri) {
|
||||||
try {
|
var error: String? = ""
|
||||||
activity?.contentResolver?.openInputStream(uri)?.use { inputStream ->
|
val wait = CountDownLatch(1)
|
||||||
ZipInputStream(inputStream).use { zip ->
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||||
var entry: ZipEntry? = zip.nextEntry
|
try {
|
||||||
val filesDir = requireContext().filesDir?.path ?: return
|
activity?.contentResolver?.openInputStream(uri)?.use { inputStream ->
|
||||||
val deviceProtectedFilesDir = DeviceProtectedUtils.getFilesDir(requireContext()).path
|
ZipInputStream(inputStream).use { zip ->
|
||||||
Settings.getInstance().stopListener()
|
var entry: ZipEntry? = zip.nextEntry
|
||||||
while (entry != null) {
|
val filesDir = requireContext().filesDir?.path ?: return@execute
|
||||||
if (entry.name.startsWith("unprotected${File.separator}")) {
|
val deviceProtectedFilesDir = DeviceProtectedUtils.getFilesDir(requireContext()).path
|
||||||
val adjustedName = entry.name.substringAfter("unprotected${File.separator}")
|
Settings.getInstance().stopListener()
|
||||||
if (backupFilePatterns.any { adjustedName.matches(it) }) {
|
while (entry != null) {
|
||||||
val targetFileName = upgradeFileNames(adjustedName)
|
if (entry.name.startsWith("unprotected${File.separator}")) {
|
||||||
val file = File(deviceProtectedFilesDir, targetFileName)
|
val adjustedName = entry.name.substringAfter("unprotected${File.separator}")
|
||||||
|
if (backupFilePatterns.any { adjustedName.matches(it) }) {
|
||||||
|
val targetFileName = upgradeFileNames(adjustedName)
|
||||||
|
val file = File(deviceProtectedFilesDir, targetFileName)
|
||||||
|
FileUtils.copyStreamToNewFile(zip, file)
|
||||||
|
}
|
||||||
|
} else if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||||
|
val targetFileName = upgradeFileNames(entry.name)
|
||||||
|
val file = File(filesDir, targetFileName)
|
||||||
FileUtils.copyStreamToNewFile(zip, file)
|
FileUtils.copyStreamToNewFile(zip, file)
|
||||||
|
} else if (entry.name == PREFS_FILE_NAME) {
|
||||||
|
val prefLines = String(zip.readBytes()).split("\n")
|
||||||
|
sharedPreferences.edit().clear().apply()
|
||||||
|
readJsonLinesToSettings(prefLines, sharedPreferences)
|
||||||
|
} else if (entry.name == PROTECTED_PREFS_FILE_NAME) {
|
||||||
|
val prefLines = String(zip.readBytes()).split("\n")
|
||||||
|
val protectedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
protectedPrefs.edit().clear().apply()
|
||||||
|
readJsonLinesToSettings(prefLines, protectedPrefs)
|
||||||
}
|
}
|
||||||
} else if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
zip.closeEntry()
|
||||||
val targetFileName = upgradeFileNames(entry.name)
|
entry = zip.nextEntry
|
||||||
val file = File(filesDir, targetFileName)
|
|
||||||
FileUtils.copyStreamToNewFile(zip, file)
|
|
||||||
} else if (entry.name == PREFS_FILE_NAME) {
|
|
||||||
val prefLines = String(zip.readBytes()).split("\n")
|
|
||||||
sharedPreferences.edit().clear().apply()
|
|
||||||
readJsonLinesToSettings(prefLines, sharedPreferences)
|
|
||||||
} else if (entry.name == PROTECTED_PREFS_FILE_NAME) {
|
|
||||||
val prefLines = String(zip.readBytes()).split("\n")
|
|
||||||
val protectedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
||||||
protectedPrefs.edit().clear().apply()
|
|
||||||
readJsonLinesToSettings(prefLines, protectedPrefs)
|
|
||||||
}
|
}
|
||||||
zip.closeEntry()
|
|
||||||
entry = zip.nextEntry
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
error = t.message
|
||||||
|
Log.w(TAG, "error during restore", t)
|
||||||
|
} finally {
|
||||||
|
wait.countDown()
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
|
||||||
// inform about every error
|
|
||||||
Log.w(TAG, "error during restore", t)
|
|
||||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, t.message))
|
|
||||||
} finally {
|
|
||||||
checkVersionUpgrade(requireContext())
|
|
||||||
Settings.getInstance().startListener()
|
|
||||||
val additionalSubtypes = Settings.readPrefAdditionalSubtypes(sharedPreferences, resources);
|
|
||||||
updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes));
|
|
||||||
reloadEnabledSubtypes(requireContext())
|
|
||||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
|
||||||
activity?.sendBroadcast(newDictBroadcast)
|
|
||||||
// reload current prefs screen
|
|
||||||
preferenceScreen.removeAll()
|
|
||||||
setupPreferences()
|
|
||||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes));
|
||||||
|
reloadEnabledSubtypes(requireContext())
|
||||||
|
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||||
|
activity?.sendBroadcast(newDictBroadcast)
|
||||||
|
// reload current prefs screen
|
||||||
|
preferenceScreen.removeAll()
|
||||||
|
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
|
// todo (later): remove this when new package name has been in use for long enough, this is only for migrating from old openboard name
|
||||||
|
|
|
@ -128,7 +128,7 @@ public final class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
if (intent.getResultCode() != Activity.RESULT_OK || intent.getData() == null) return;
|
if (intent.getResultCode() != Activity.RESULT_OK || intent.getData() == null) return;
|
||||||
final Uri uri = intent.getData().getData();
|
final Uri uri = intent.getData().getData();
|
||||||
if (uri != null)
|
if (uri != null)
|
||||||
saveCrashReport(uri);
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> saveCrashReport(uri));
|
||||||
});
|
});
|
||||||
|
|
||||||
private void saveCrashReport(final Uri 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.SimpleKeyboardParser
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.common.FileUtils
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
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"))
|
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
|
||||||
val layoutContent: String
|
val layoutContent: String
|
||||||
try {
|
try {
|
||||||
val i = context.contentResolver.openInputStream(uri)
|
val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile")
|
||||||
layoutContent = i?.use { it.reader().readText() } ?: throw IOException()
|
FileUtils.copyContentUriToNewFile(uri, context, tmpFile)
|
||||||
|
layoutContent = tmpFile.readText()
|
||||||
|
tmpFile.delete()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file"))
|
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()
|
cachedDictionaryFile.delete()
|
||||||
try {
|
try {
|
||||||
val i = context.contentResolver.openInputStream(uri)
|
FileUtils.copyContentUriToNewFile(uri, context, cachedDictionaryFile)
|
||||||
FileUtils.copyStreamToNewFile(i, cachedDictionaryFile)
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue