Merge pull request #1699 from keymanapp/android-register-model

[Android] Store a lexical models list and register lexical models to KMW
This commit is contained in:
Darcy Wong 2019-03-28 15:58:28 +07:00 committed by GitHub
commit fefdc7b7c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 273 additions and 32 deletions

View file

@ -830,6 +830,14 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi
}
}
@Override
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
for(int i=0; i<lexicalModelsInstalled.size(); i++) {
HashMap<String, String>lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i));
KMManager.addLexicalModel(this, lexicalModelInfo);
}
}
private void copyFile(FileInputStream inStream, File dstFile) throws IOException {
OutputStream outStream = new FileOutputStream(dstFile);

View file

@ -182,26 +182,37 @@ public class PackageActivity extends AppCompatActivity {
try {
if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) {
// processKMP will remove currently installed package and install
installedPackageKeyboards = kmpProcessor.processKMP(kmpFile, tempPackagePath, PackageProcessor.PP_KEYBOARDS_KEY);
installedPackageKeyboards = kmpProcessor.processKMP(kmpFile, tempPackagePath,
PackageProcessor.PP_KEYBOARDS_KEY);
// Do the notifications!
boolean success = installedPackageKeyboards.size() != 0;
if (success) {
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED, installedPackageKeyboards, 1);
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED,
installedPackageKeyboards, 1);
if (installedPackageKeyboards != null) {
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED, installedPackageKeyboards, 1);
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED,
installedPackageKeyboards, 1);
}
cleanup();
} else {
showErrorDialog(context, pkgId, getString(R.string.no_new_touch_keyboards_to_install));
}
} else if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) {
installedLexicalModels = kmpProcessor.processKMP(kmpFile, tempPackagePath, PackageProcessor.PP_LEXICAL_MODELS_KEY);
installedLexicalModels = kmpProcessor.processKMP(kmpFile, tempPackagePath,
PackageProcessor.PP_LEXICAL_MODELS_KEY);
// Do the notifications
boolean success = installedLexicalModels.size() != 0;
if (success) {
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
installedLexicalModels, 1);
if (installedLexicalModels != null) {
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
installedLexicalModels, 1);
}
cleanup();
} else {
showErrorDialog(context, pkgId, getString(R.string.no_new_predictive_text_to_install));
}
cleanup();
}
} catch (Exception e) {
Log.e("PackageActivity", "Error " + e);
@ -280,12 +291,20 @@ public class PackageActivity extends AppCompatActivity {
alertDialog.show();
}
void notifyPackageInstallListeners(KeyboardEventHandler.EventType eventType, List<Map<String, String>> keyboards, int result) {
void notifyPackageInstallListeners(KeyboardEventHandler.EventType eventType,
List<Map<String, String>> keyboards, int result) {
if (kbDownloadEventListeners != null) {
KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, keyboards, result);
}
}
void notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType eventType,
List<Map<String, String>> models, int result) {
if (kbDownloadEventListeners != null) {
KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, models, result);
}
}
public static void addKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
if (kbDownloadEventListeners == null) {
kbDownloadEventListeners = new ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener>();

View file

@ -49,6 +49,7 @@
<string name="install_keyboard_package" translatable="false">Install Keyboard Package</string>
<string name="install_predictive_text_package" translatable="false">Install Predictive Text Package</string>
<string name="no_new_touch_keyboards_to_install" translatable="false">No new touch-optimized keyboards to install</string>
<string name="no_new_predictive_text_to_install" translatable="false">No new predictive text to install</string>
<string name="no_valid_touch_keyboards_to_install" translatable="false">No valid touch-optimized keyboards to install</string>
<string name="no_targets_to_install" translatable="false">No keyboards or predictive text to install</string>
<string name="missing_metadata" translatable="false">kmp.json does not exist</string>

View file

@ -126,6 +126,12 @@
window.jsInterface.insertText(dn, s);
}
function registerModel(model) {
var kmw=window['keyman'];
window.console.log('model: ' + model);
kmw.registerModel(model);
}
function resetContext() {
var kmw=window['keyman'];
kmw.resetContext();

View file

@ -367,10 +367,16 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
String pkgTarget = kmpProcessor.getPackageTarget(kmpFile);
if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) {
File unzipPath = kmpProcessor.unzipKMP(kmpFile);
// TODO: Propogate installedLexicalModels to KMW
List<Map<String, String>> installedLexicalModels =
kmpProcessor.processKMP(kmpFile, unzipPath, PackageProcessor.PP_LEXICAL_MODELS_KEY);
boolean success = installedLexicalModels.size() != 0;
if (success) {
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
installedLexicalModels, 1);
}
}
}
if (result < 0) {
if (FileUtils.hasFontExtension(url)) {
@ -407,6 +413,19 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, keyboardInfo, result);
}
}
/**
* Notify listeners when a lexical model is installed
* @param eventType
* @param models
* @param result
*/
protected void notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType eventType,
List<Map<String, String>> models, int result) {
if (kbDownloadEventListeners != null) {
KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, models, result);
}
}
}
/**

View file

@ -57,6 +57,8 @@ import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.KMScanCodeMap;
import com.tavultesoft.kmea.util.FileUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public final class KMManager {
@ -154,6 +156,7 @@ public final class KMManager {
protected static final String KMFilename_Osk_Ttf_Font = "keymanweb-osk.ttf";
protected static final String KMFilename_Osk_Woff_Font = "keymanweb-osk.woff";
public static final String KMFilename_KeyboardsList = "keyboards_list.dat";
public static final String KMFilename_LexicalModelsList = "lexical_models_list.dat";
private static Context appContext;
@ -647,6 +650,41 @@ public final class KMManager {
return KeyboardPickerActivity.getKeyboardsList(context);
}
public static boolean registerLexicalModel(HashMap<String, String> lexicalModelInfo) {
String pkgID = lexicalModelInfo.get(KMKey_PackageID);
String modelID = lexicalModelInfo.get(KMKey_LexicalModelID);
String languageID = lexicalModelInfo.get(KMKey_LanguageID);
String path = "file://" + getLexicalModelsDir() + pkgID + File.separator + modelID + ".model.js";
JSONObject modelObj = new JSONObject();
JSONArray languageJSONArray = new JSONArray();
try {
modelObj.put("id", modelID);
languageJSONArray.put(languageID);
modelObj.put("languages", languageJSONArray);
modelObj.put("path", path);
} catch (JSONException e) {
Log.e(TAG, "Invalid lexical model to register");
return false;
}
// Use single quotes in JSONobject for javascript call
String model = String.valueOf(modelObj);
model = model.replaceAll("\"", "'");
if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) {
InAppKeyboard.loadUrl(String.format("javascript:registerModel(%s)", model));
}
if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) {
SystemKeyboard.loadUrl(String.format("javascript:registerModel(%s)", model));
}
return true;
}
public static boolean addLexicalModel(Context context, HashMap<String, String> lexicalModelInfo) {
return KeyboardPickerActivity.addLexicalModel(context, lexicalModelInfo);
}
public static boolean addKeyboard(Context context, HashMap<String, String> keyboardInfo) {
// Log Firebase analytic event.
Bundle params = new Bundle();

View file

@ -20,7 +20,8 @@ public final class KeyboardEventHandler {
KEYBOARD_DISMISSED,
KEYBOARD_DOWNLOAD_STARTED,
KEYBOARD_DOWNLOAD_FINISHED,
PACKAGE_INSTALLED;
PACKAGE_INSTALLED,
LEXICAL_MODEL_INSTALLED;
}
public static void notifyListeners(ArrayList<OnKeyboardEventListener> listeners, KeyboardType keyboardType, EventType event, String newValue) {
@ -44,11 +45,13 @@ public final class KeyboardEventHandler {
}
}
public static void notifyListeners(ArrayList<OnKeyboardDownloadEventListener> listeners, EventType event, HashMap<String, String> keyboardInfo, int result) {
public static void notifyListeners(ArrayList<OnKeyboardDownloadEventListener> listeners,
EventType event, HashMap<String, String> keyboardInfo, int result) {
if (listeners != null) {
@SuppressWarnings("unchecked")
// make a copy of the list to avoid concurrent modification while iterating
ArrayList<OnKeyboardDownloadEventListener> _listeners = (ArrayList<OnKeyboardDownloadEventListener>) listeners.clone();
ArrayList<OnKeyboardDownloadEventListener> _listeners =
(ArrayList<OnKeyboardDownloadEventListener>) listeners.clone();
if (event == EventType.KEYBOARD_DOWNLOAD_STARTED) {
for (OnKeyboardDownloadEventListener listener : _listeners)
listener.onKeyboardDownloadStarted(keyboardInfo);
@ -59,14 +62,22 @@ public final class KeyboardEventHandler {
}
}
public static void notifyListeners(ArrayList<OnKeyboardDownloadEventListener> listeners, EventType event, List<Map<String, String>> keyboardsInfo, int result) {
public static void notifyListeners(ArrayList<OnKeyboardDownloadEventListener> listeners,
EventType event, List<Map<String, String>> info, int result) {
if (listeners != null) {
@SuppressWarnings("unchecked")
// make a copy of the list to avoid concurrent modification while iterating
ArrayList<OnKeyboardDownloadEventListener> _listeners = (ArrayList<OnKeyboardDownloadEventListener>) listeners.clone();
ArrayList<OnKeyboardDownloadEventListener> _listeners =
(ArrayList<OnKeyboardDownloadEventListener>) listeners.clone();
if (event == EventType.PACKAGE_INSTALLED) {
for (OnKeyboardDownloadEventListener listener : _listeners)
listener.onPackageInstalled(keyboardsInfo);
for (OnKeyboardDownloadEventListener listener : _listeners) {
listener.onPackageInstalled(info);
}
} else if (event == EventType.LEXICAL_MODEL_INSTALLED) {
for (OnKeyboardDownloadEventListener listener : _listeners) {
listener.onLexicalModelInstalled(info);
}
}
}
}
@ -88,5 +99,7 @@ public final class KeyboardEventHandler {
void onKeyboardDownloadFinished(HashMap<String, String> keyboardInfo, int result); // result > 0 if successful, < 0 if failed
void onPackageInstalled(List<Map<String, String>> keyboardsInstalled);
void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled);
}
}

View file

@ -185,4 +185,9 @@ public final class KeyboardListActivity extends AppCompatActivity implements OnK
public void onPackageInstalled(List<Map<String, String>> keyboardsInstalled) {
// Do nothing.
}
}
@Override
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
// Do nothing.
}
}

View file

@ -58,6 +58,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
private static KMKeyboardPickerAdapter listAdapter = null;
private static ArrayList<HashMap<String, String>> keyboardsList = null;
private static HashMap<String, String> keyboardVersions = null;
private static ArrayList<HashMap<String, String>> lexicalModelsList = null;
// private static HashMap<String, String> lexicalModelsVersions = null; TODO
private static boolean checkingUpdates = false;
private static int updateCount = 0;
private static int failedUpdateCount = 0;
@ -125,9 +127,22 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
keyboardsList.add(kbInfo);
// We'd prefer not to overwrite a file if it exists
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), KMManager.KMFilename_KeyboardsList);
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE),
KMManager.KMFilename_KeyboardsList);
if (!file.exists()) {
saveKeyboardsList(context);
saveList(context, KMManager.KMFilename_KeyboardsList);
}
}
lexicalModelsList = getLexicalModelsList(context);
if (lexicalModelsList == null) {
lexicalModelsList = new ArrayList<HashMap<String, String>>();
// We'd prefer not to overwrite a file if it exists
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE),
KMManager.KMFilename_LexicalModelsList);
if (!file.exists()) {
saveList(context, KMManager.KMFilename_LexicalModelsList);
}
}
@ -315,17 +330,21 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
return false;
}
private static boolean saveKeyboardsList(Context context) {
private static boolean saveList(Context context, String listName) {
boolean result;
try {
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), KMManager.KMFilename_KeyboardsList);
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), listName);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
outputStream.writeObject(keyboardsList);
if (listName.equalsIgnoreCase(KMManager.KMFilename_KeyboardsList)) {
outputStream.writeObject(keyboardsList);
} else if (listName.equalsIgnoreCase(KMManager.KMFilename_LexicalModelsList)) {
outputStream.writeObject(lexicalModelsList);
}
outputStream.flush();
outputStream.close();
result = true;
} catch (Exception e) {
Log.e(TAG, "Failed to save keyboards list. Error: " + e);
Log.e(TAG, "Failed to save " + listName + ". Error: " + e);
result = false;
}
@ -335,7 +354,14 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
protected static boolean updateKeyboardsList(Context context, ArrayList<HashMap<String, String>> list) {
boolean result;
keyboardsList = list;
result = saveKeyboardsList(context);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
return result;
}
protected static boolean updateLexicalModelsList(Context context, ArrayList<HashMap<String, String>> list) {
boolean result;
lexicalModelsList = list;
result = saveList(context, KMManager.KMFilename_LexicalModelsList);
return result;
}
@ -359,6 +385,13 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
String kFont = kbInfo.get(KMManager.KMKey_Font);
String kOskFont = kbInfo.get(KMManager.KMKey_OskFont);
KMManager.setKeyboard(pkgId, kbId, langId, kbName, langName, kFont, kOskFont);
// Register associated lexical model
HashMap<String, String> lmInfo = getAssociatedLexicalModel(langId);
if (lmInfo != null) {
KMManager.registerLexicalModel(lmInfo);
}
}
protected static boolean addKeyboard(Context context, HashMap<String, String> keyboardInfo) {
@ -382,10 +415,10 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
int x = getKeyboardIndex(context, kbKey);
if (x >= 0) {
keyboardsList.set(x, keyboardInfo);
result = saveKeyboardsList(context);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
} else {
keyboardsList.add(keyboardInfo);
result = saveKeyboardsList(context);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
if (!result) {
keyboardsList.remove(keyboardsList.size() - 1);
}
@ -397,6 +430,39 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
return result;
}
protected static boolean addLexicalModel(Context context, HashMap<String, String> lexicalModelInfo) {
boolean result = false;
if (lexicalModelsList == null) {
lexicalModelsList = new ArrayList<HashMap<String, String>>();
}
if (lexicalModelInfo != null) {
String pkgID = lexicalModelInfo.get(KMManager.KMKey_PackageID);
String modelID = lexicalModelInfo.get(KMManager.KMKey_LexicalModelID);
String langID = lexicalModelInfo.get(KMManager.KMKey_LanguageID);
if (pkgID != null && modelID != null && langID != null) {
String lmKey = String.format("%s_%s_%s", langID, pkgID, modelID);
if (lmKey.length() >= 3) {
int x = getLexicalModelIndex(context, lmKey);
if (x >= 0) {
lexicalModelsList.set(x, lexicalModelInfo);
result = saveList(context, KMManager.KMFilename_LexicalModelsList);
} else {
lexicalModelsList.add(lexicalModelInfo);
result = saveList(context, KMManager.KMFilename_LexicalModelsList);
if (!result) {
lexicalModelsList.remove(lexicalModelsList.size() - 1);
}
}
}
}
}
return result;
}
protected static boolean removeKeyboard(Context context, int position) {
boolean result = false;
if (keyboardsList == null) {
@ -405,7 +471,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
if (keyboardsList != null && position >= 0 && position < keyboardsList.size()) {
keyboardsList.remove(position);
result = saveKeyboardsList(context);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
}
return result;
@ -430,17 +496,17 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
@SuppressWarnings("unchecked")
protected static ArrayList<HashMap<String, String>> getKeyboardsList(Context context) {
private static ArrayList<HashMap<String, String>> getList(Context context, String filename) {
ArrayList<HashMap<String, String>> list = null;
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), KMManager.KMFilename_KeyboardsList);
File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), filename);
if (file.exists()) {
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
list = (ArrayList<HashMap<String, String>>) inputStream.readObject();
inputStream.close();
} catch (Exception e) {
Log.e(TAG, "Failed to read keyboards list. Error: " + e);
Log.e(TAG, "Failed to read " + filename + ". Error: " + e);
list = null;
}
}
@ -448,6 +514,14 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
return list;
}
protected static ArrayList<HashMap<String, String>> getKeyboardsList(Context context) {
return getList(context, KMManager.KMFilename_KeyboardsList);
}
protected static ArrayList<HashMap<String, String>> getLexicalModelsList(Context context) {
return getList(context, KMManager.KMFilename_LexicalModelsList);
}
protected static boolean containsKeyboard(Context context, String keyboardKey) {
if (keyboardsList == null) {
keyboardsList = getKeyboardsList(context);
@ -524,6 +598,45 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
return index;
}
protected static int getLexicalModelIndex(Context context, String lexicalModelKey) {
int index = -1;
if (lexicalModelsList == null) {
lexicalModelsList = getLexicalModelsList(context);
}
if (lexicalModelsList != null) {
int length = lexicalModelsList.size();
for (int i=0; i < length; i++) {
HashMap<String, String> lmInfo = lexicalModelsList.get(i);
String langId = lmInfo.get(KMManager.KMKey_LanguageID);
String pkgId = lmInfo.get(KMManager.KMKey_PackageID);
String lmId = lmInfo.get(KMManager.KMKey_LexicalModelID);
String lmKey = String.format("%s_%s_%s", langId, pkgId, lmId);
if (lmKey.equals(lexicalModelKey)) {
index = i;
break;
}
}
}
return index;
}
protected static HashMap<String, String> getAssociatedLexicalModel(String langId) {
if (lexicalModelsList != null) {
int length = lexicalModelsList.size();
for (int i = 0; i < length; i++) {
HashMap<String, String> lmInfo = lexicalModelsList.get(i);
if (langId.equalsIgnoreCase(lmInfo.get(KMManager.KMKey_LanguageID))) {
return lmInfo;
}
}
}
return null;
}
protected static HashMap<String, String> getKeyboardInfo(Context context, int index) {
if (index < 0) {
return null;
@ -751,7 +864,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
index = getKeyboardIndex(this, kbKey);
}
keyboardsList.set(index, keyboardInfo);
saveKeyboardsList(this);
saveList(this, KMManager.KMFilename_KeyboardsList);
} else if (result < 0) {
failedUpdateCount++;
}
@ -795,4 +908,9 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
public void onPackageInstalled(List<Map<String, String>> keyboardsInstalled) {
// Do nothing
}
}
@Override
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
// Do nothing
}
}

View file

@ -164,6 +164,11 @@ public final class LanguageListActivity extends AppCompatActivity implements OnK
// Do nothing.
}
@Override
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
// Do nothing.
}
protected static HashMap<String, String> getKeyboardInfo(int languageIndex, int keyboardIndex) {
if (languages == null)
return null;

View file

@ -351,12 +351,21 @@ namespace com.keyman.text {
}
};
/**
* Register a lexical model
*
* @param {com.keyman.text.prediction.ModelSpec} model Spec of the lexical model
*/
keymanweb['registerModel']=function(model: com.keyman.text.prediction.ModelSpec) {
keymanweb.modelManager.register(model);
}
/**
* Function called by iOS when a device-implemented keyboard popup is displayed or hidden
*
* @param {boolean} isVisible
*
**/
**/
keymanweb['popupVisible'] = function(isVisible)
{
osk.vkbd.popupVisible = isVisible;