register app for opening .dict files (content uri only)

This commit is contained in:
Helium314 2023-08-29 22:26:57 +02:00
parent e623d14829
commit e3240965a8
6 changed files with 183 additions and 80 deletions

View file

@ -111,6 +111,22 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<!-- this seems to require files permission, so better don't do it
todo: consider adding read storage permission, and ask if the user opens a file
but
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" android:scheme="file" android:host="*" android:pathPattern=".*\\.dict" />
</intent-filter>
-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" android:scheme="content" android:host="*" android:pathPattern=".*\\.dict" />
</intent-filter>
</activity>
<activity android:name=".spellcheck.SpellCheckerSettingsActivity"

View file

@ -15,12 +15,9 @@ import androidx.core.view.isVisible
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader
import org.dslul.openboard.inputmethod.latin.utils.*
import java.io.File
import java.io.IOException
import java.util.*
import kotlin.collections.HashSet
@ -37,7 +34,6 @@ class LanguageSettingsDialog(
private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null)
private val mainLocaleString = subtypes.first().subtype.locale
private val mainLocale = mainLocaleString.toLocale()
private val cachedDictionaryFile by lazy { File(context.cacheDir.path + File.separator + "temp_dict") }
init {
setTitle(subtypes.first().displayName)
@ -207,82 +203,10 @@ class LanguageSettingsDialog(
}
override fun onNewDictionary(uri: Uri?) {
if (uri == null)
return onDictionaryLoadingError(R.string.dictionary_load_error)
cachedDictionaryFile.delete()
try {
FileUtils.copyStreamToNewFile(
context.contentResolver.openInputStream(uri),
cachedDictionaryFile
)
} catch (e: IOException) {
return onDictionaryLoadingError(R.string.dictionary_load_error)
}
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
?: return onDictionaryLoadingError(R.string.dictionary_file_error)
val locale = newHeader.mLocaleString.toLocale()
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not,
// e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible
if (ScriptUtils.getScriptFromSpellCheckerLocale(locale) != ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale))
return onDictionaryLoadingError(R.string.dictionary_file_wrong_script)
if (locale != mainLocale) {
val message = context.resources.getString(
R.string.dictionary_file_wrong_locale,
LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context),
LocaleUtils.getLocaleDisplayNameInSystemLocale(mainLocale, context)
)
Builder(context)
.setMessage(message)
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
.setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ ->
addDictAndAskToReplace(newHeader)
}
.show()
return
}
addDictAndAskToReplace(newHeader)
}
private fun addDictAndAskToReplace(header: DictionaryHeader) {
val dictionaryType = header.mIdString.substringBefore(":")
val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) +
File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX
val dictFile = File(dictFilename)
fun moveDict(replaced: Boolean) {
if (!cachedDictionaryFile.renameTo(dictFile)) {
return onDictionaryLoadingError(R.string.dictionary_load_error)
}
if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) {
// replaced main dict, remove the one created from internal data
// todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles
// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) +
// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString)
// File(internalMainDictFilename).delete()
}
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
fragment?.activity?.sendBroadcast(newDictBroadcast)
NewDictionaryAdder(context) { replaced, dictFile ->
if (!replaced)
addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries))
}
if (!dictFile.exists()) {
return moveDict(false)
}
confirmDialog(context, context.getString(R.string.replace_dictionary_message, dictionaryType), context.getString(
R.string.replace_dictionary)) {
moveDict(true)
}
}
private fun onDictionaryLoadingError(messageId: Int) = onDictionaryLoadingError(context.getString(messageId))
private fun onDictionaryLoadingError(message: String) {
cachedDictionaryFile.delete()
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}.addDictionary(uri, mainLocale)
}
private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) {
@ -395,5 +319,4 @@ private fun getAvailableDictionaryLocales(context: Context, mainLocaleString: St
return locales
}
private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)
private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries"

View file

@ -23,6 +23,7 @@ import android.preference.PreferenceActivity;
import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager;
import org.dslul.openboard.inputmethod.latin.utils.FragmentUtils;
import org.dslul.openboard.inputmethod.latin.utils.NewDictionaryAdder;
import androidx.core.app.ActivityCompat;
@ -46,6 +47,10 @@ public final class SettingsActivity extends PreferenceActivity
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
final Intent i = getIntent();
if (Intent.ACTION_VIEW.equals(i.getAction()) && i.getData() != null) {
new NewDictionaryAdder(this, null).addDictionary(i.getData(), null);
}
}
@Override

View file

@ -133,6 +133,16 @@ fun getSystemLocales(): List<Locale> {
return systemLocales
}
fun hasMatchingSubtypeForLocaleString(localeString: String): Boolean {
require(initialized)
return !resourceSubtypesByLocale[localeString].isNullOrEmpty()
}
fun getAvailableSubtypeLocaleStrings(): Collection<String> {
require(initialized)
return resourceSubtypesByLocale.keys
}
fun init(context: Context) {
if (initialized) return
SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName

View file

@ -0,0 +1,143 @@
package org.dslul.openboard.inputmethod.latin.utils
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader
import org.dslul.openboard.inputmethod.latin.settings.*
import java.io.File
import java.io.IOException
import java.util.*
class NewDictionaryAdder(private val context: Context, private val onAdded: ((Boolean, File) -> Unit)?) {
private val cachedDictionaryFile = File(context.cacheDir.path + File.separator + "temp_dict")
fun addDictionary(uri: Uri?, mainLocale: Locale?) {
if (uri == null)
return onDictionaryLoadingError(R.string.dictionary_load_error)
cachedDictionaryFile.delete()
try {
val i = context.contentResolver.openInputStream(uri)
FileUtils.copyStreamToNewFile(
i,
cachedDictionaryFile
)
} catch (e: IOException) {
return onDictionaryLoadingError(R.string.dictionary_load_error)
}
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
?: return onDictionaryLoadingError(R.string.dictionary_file_error)
val locale = newHeader.mLocaleString.toLocale()
if (mainLocale == null) {
val localeName = LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context)
val message = context.getString(R.string.add_new_dictionary_ask_locale,
newHeader.mIdString.substringBefore(":"),
localeName
)
val b = AlertDialog.Builder(context)
.setTitle(R.string.add_new_dictionary_title)
.setMessage(message)
.setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
.setNegativeButton(R.string.button_select_language) { _, _ -> selectLocaleForDictionary(newHeader, locale) }
if (hasMatchingSubtypeForLocaleString(locale.toString())) {
val buttonText = context.getString(R.string.button_add_to_language, localeName)
b.setPositiveButton(buttonText) { _, _ ->
addDictAndAskToReplace(newHeader, locale)
}
}
b.show()
return
}
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not,
// e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible
if (ScriptUtils.getScriptFromSpellCheckerLocale(locale) != ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale))
return onDictionaryLoadingError(R.string.dictionary_file_wrong_script)
if (locale != mainLocale) {
val message = context.resources.getString(
R.string.dictionary_file_wrong_locale,
LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context),
LocaleUtils.getLocaleDisplayNameInSystemLocale(mainLocale, context)
)
AlertDialog.Builder(context)
.setMessage(message)
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
.setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ ->
addDictAndAskToReplace(newHeader, mainLocale)
}
.show()
return
}
addDictAndAskToReplace(newHeader, mainLocale)
}
private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) {
val localeStrings = getAvailableSubtypeLocaleStrings()
val locales = linkedSetOf<Locale>()
localeStrings.forEach {
if (it.substringBefore("_") == dictLocale.language)
locales.add(it.toLocale())
}
localeStrings.forEach { locales.add(it.toLocale()) }
val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray()
AlertDialog.Builder(context)
.setTitle(R.string.button_select_language)
.setItems(displayNamesArray) { di, i ->
di.dismiss()
locales.forEachIndexed { index, locale ->
if (index == i)
addDictAndAskToReplace(newHeader, locale)
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
.show()
}
private fun addDictAndAskToReplace(header: DictionaryHeader, mainLocale: Locale) {
val dictionaryType = header.mIdString.substringBefore(":")
val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale.toString(), context) +
File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX
val dictFile = File(dictFilename)
fun moveDict(replaced: Boolean) {
if (!cachedDictionaryFile.renameTo(dictFile)) {
return onDictionaryLoadingError(R.string.dictionary_load_error)
}
if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) {
// replaced main dict, remove the one created from internal data
// todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles
// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) +
// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString)
// File(internalMainDictFilename).delete()
}
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
context.sendBroadcast(newDictBroadcast)
onAdded?.let { it(replaced, dictFile) }
}
if (!dictFile.exists()) {
return moveDict(false)
}
confirmDialog(context, context.getString(R.string.replace_dictionary_message, dictionaryType), context.getString(
R.string.replace_dictionary)) {
moveDict(true)
}
}
private fun onDictionaryLoadingError(messageId: Int) {
cachedDictionaryFile.delete()
Toast.makeText(context, messageId, Toast.LENGTH_LONG).show()
}
}
fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)

View file

@ -491,12 +491,18 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM
<string name="internal_dictionary_summary">Built-in dictionary</string>
<!-- Title for the adding new user dictionary dialog -->
<string name="add_new_dictionary_title">"Add dictionary from file"</string>
<!-- Message when opening a dictionary file, showing type and dictionary language, prompting to choose a language to add it to -->
<string name="add_new_dictionary_ask_locale">"To which language should the dictionary \"%1$s\" for %2$s be added?"</string>
<!-- Button in dialog for choosing a locale -->
<string name="button_select_language">"Select language"</string>
<!-- Button in dialog for adding dictionary to specific locale -->
<string name="button_add_to_language">"Add to %s"</string>
<!-- Message for user dictionary replacement dialog -->
<string name="replace_dictionary_message">"Really replace user-added dictionary \"%s\"?"</string>
<!-- Title and confirm button text for user dictionary replacement dialog -->
<string name="replace_dictionary">"Replace dictionary"</string>
<!-- Message for user dictionary remove dialog -->
<string name="remove_dictionary_message">"Really remove user-added dictionary \"%1$s\"?"</string>
<string name="remove_dictionary_message">"Really remove user-added dictionary \"%s\"?"</string>
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
<string name="add_dictionary">"Select a dictionary to add. Dictionaries in .dict format can be downloaded %s."</string>
<!-- Title of the link to the download page inserted into selection message (above) -->