diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index 46bdbb6780..1049fc750d 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -856,7 +856,8 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi
kbInfo.put(KMManager.KMKey_OskFont, kOskFont);
if (i == 0) {
if (KMManager.addKeyboard(this, kbInfo)) {
- KMManager.setKeyboard(packageID, keyboardID, langId, keyboardName, langName, kFont, kOskFont);
+ if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
+ KMManager.setKeyboard(packageID, keyboardID, langId, keyboardName, langName, kFont, kOskFont);
}
} else {
KMManager.addKeyboard(this, kbInfo);
@@ -864,7 +865,8 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi
}
} else {
if (KMManager.addKeyboard(this, keyboardInfo)) {
- KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
+ if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
+ KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
}
}
} else {
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java
index 766557ce89..d4c2b036ab 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java
@@ -8,6 +8,7 @@ import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
+import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
@@ -19,6 +20,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import com.tavultesoft.kmea.data.CloudApiTypes;
+import com.tavultesoft.kmea.data.CloudDataJsonUtil;
+import com.tavultesoft.kmea.data.CloudDownloadMgr;
+import com.tavultesoft.kmea.data.CloudKeyboardDataDownloadCallback;
+import com.tavultesoft.kmea.data.CloudKeyboardMetaDataDownloadCallback;
+import com.tavultesoft.kmea.data.CloudLexicalPackageDownloadCallback;
import com.tavultesoft.kmea.packages.LexicalModelPackageProcessor;
import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.util.FileUtils;
@@ -29,7 +36,11 @@ import static com.tavultesoft.kmea.KMManager.KMDefault_UndefinedPackageID;
public class KMKeyboardDownloaderActivity extends AppCompatActivity {
// Bundle Keys
+
// Cloud
+ //TODO: Should be removed with the old implementation when downloadmanager impl works
+ public static boolean USE_DOWNLOAD_MANAGER = true;
+
public static final String ARG_PKG_ID = "KMKeyboardActivity.pkgID";
public static final String ARG_KB_ID = "KMKeyboardActivity.kbID";
public static final String ARG_LANG_ID = "KMKeyboardActivity.langID";
@@ -66,6 +77,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri";
public static final String KMKey_FontBaseURI = "fontBaseUri";
+ //TODO: use keyboard model class, should not be static
private static String pkgID;
private static String kbID;
private static String langID;
@@ -89,43 +101,41 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
Bundle bundle = getIntent().getExtras();
- if (bundle != null) {
- pkgID = bundle.getString(ARG_PKG_ID);
- if (pkgID == null || pkgID.isEmpty()) {
- pkgID = KMManager.KMDefault_UndefinedPackageID;
- }
- langID = bundle.getString(ARG_LANG_ID);
- langName = bundle.getString(ARG_LANG_NAME);
-
- downloadOnlyLexicalModel = bundle.containsKey(ARG_MODEL_URL) &&
- bundle.getString(ARG_MODEL_URL) != null &&
- !bundle.getString(ARG_MODEL_URL).isEmpty();
-
- if (downloadOnlyLexicalModel) {
- modelID = bundle.getString(ARG_MODEL_ID);
- modelName = bundle.getString(ARG_MODEL_NAME);
- isCustom = false;
- url = bundle.getString(ARG_MODEL_URL);
- } else {
-
- kbID = bundle.getString(ARG_KB_ID);
- kbName = bundle.getString(ARG_KB_NAME);
- isCustom = bundle.getBoolean(ARG_IS_CUSTOM);
-
- // URL parameters for custom keyboard (if they exist)
- customKeyboard = bundle.getString(ARG_KEYBOARD);
- customLanguage = bundle.getString(ARG_LANGUAGE);
- url = bundle.getString(ARG_URL);
- filename = bundle.getString(ARG_FILENAME);
- if (filename == null || filename.isEmpty()) {
- filename = "unknown";
- }
- }
- } else {
+ if (bundle == null)
return;
+
+ pkgID = bundle.getString(ARG_PKG_ID);
+ if (pkgID == null || pkgID.isEmpty()) {
+ pkgID = KMManager.KMDefault_UndefinedPackageID;
+ }
+ langID = bundle.getString(ARG_LANG_ID);
+ langName = bundle.getString(ARG_LANG_NAME);
+
+ downloadOnlyLexicalModel = bundle.containsKey(ARG_MODEL_URL) &&
+ bundle.getString(ARG_MODEL_URL) != null &&
+ !bundle.getString(ARG_MODEL_URL).isEmpty();
+
+ if (downloadOnlyLexicalModel) {
+ modelID = bundle.getString(ARG_MODEL_ID);
+ modelName = bundle.getString(ARG_MODEL_NAME);
+ isCustom = false;
+ url = bundle.getString(ARG_MODEL_URL);
+ } else {
+
+ kbID = bundle.getString(ARG_KB_ID);
+ kbName = bundle.getString(ARG_KB_NAME);
+ isCustom = bundle.getBoolean(ARG_IS_CUSTOM);
+
+ // URL parameters for custom keyboard (if they exist)
+ customKeyboard = bundle.getString(ARG_KEYBOARD);
+ customLanguage = bundle.getString(ARG_LANGUAGE);
+ url = bundle.getString(ARG_URL);
+ filename = bundle.getString(ARG_FILENAME);
+ if (filename == null || filename.isEmpty()) {
+ filename = "unknown";
+ }
}
- Bundle args = new Bundle();
String title = "";
if (url != null) {
title = String.format("%s: %s", getString(R.string.custom_keyboard), filename);
@@ -162,6 +172,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
* Used by the download method to handle asynchronous downloading and evaluation of
* packages and keyboards.
*/
+ @Deprecated
static class DownloadTask extends AsyncTask {
static class Result {
public final Integer kbdResult;
@@ -247,6 +258,8 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
String remoteUrl = "",remoteLexicalModelUrl = "";
if (isCustom) {
+ //TODO: Doesn't work, because this case will end up in an exception during download???
+ // See downloadNonKMPKeyboard
remoteUrl = url;
} else {
// Keyman cloud
@@ -357,10 +370,9 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
}
String fontBaseUri = options.optString(KMKey_FontBaseURI, "");
- JSONObject language, keyboard = null;
// Keyman cloud keyboard distribution via JSON
- language = kbData.optJSONObject(KMKey_Language);
+ JSONObject language = kbData.optJSONObject(KMKey_Language);
if (language == null) {
throw new Exception(exceptionStr);
}
@@ -368,20 +380,8 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
langName = language.optString(KMManager.KMKey_Name, "");
JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards);
- if (keyboards == null) {
- throw new Exception(exceptionStr);
- }
- // In case keyboards array contains multiple keyboards, get the one with matching keyboard ID
- for (int index = 0; index < keyboards.length(); index++) {
- keyboard = keyboards.getJSONObject(index);
- if (keyboard != null && (kbID.equals(keyboard.getString(KMManager.KMKey_ID)))) {
- break;
- }
- }
- if (keyboard == null) {
- throw new Exception(exceptionStr);
- }
+ JSONObject keyboard = CloudDataJsonUtil.findMatchingKeyboardByID(keyboards,kbID);
kbID = keyboard.getString(KMManager.KMKey_ID);
pkgID = keyboard.optString(KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID);
@@ -400,13 +400,13 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
JSONObject jsonOskFont = keyboard.optJSONObject(KMManager.KMKey_OskFont);
if (jsonFont != null) {
- findTTF(jsonFont);
+ CloudDataJsonUtil.updateFontSourceToTTFFont(jsonFont);
}
if (jsonOskFont != null) {
- findTTF(jsonOskFont);
+ CloudDataJsonUtil.updateFontSourceToTTFFont(jsonOskFont);
}
- ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true);
- ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true);
+ ArrayList fontUrls = CloudDataJsonUtil.fontUrls(jsonFont, fontBaseUri, false);
+ ArrayList oskFontUrls = CloudDataJsonUtil.fontUrls(jsonOskFont, fontBaseUri, true);
if (fontUrls != null)
urls.addAll(fontUrls);
if (oskFontUrls != null) {
@@ -502,17 +502,9 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
*/
protected void notifyListeners(KeyboardEventHandler.EventType eventType, int result) {
if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMManager.KMKey_PackageID, pkgID);
- keyboardInfo.put(KMManager.KMKey_KeyboardID, kbID);
- keyboardInfo.put(KMManager.KMKey_LanguageID, langID);
- keyboardInfo.put(KMManager.KMKey_KeyboardName, kbName);
- keyboardInfo.put(KMManager.KMKey_LanguageName, langName);
- keyboardInfo.put(KMManager.KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMManager.KMKey_CustomKeyboard, kbIsCustom);
- keyboardInfo.put(KMManager.KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMManager.KMKey_OskFont, oskFont);
+ HashMap keyboardInfo =
+ CloudDataJsonUtil.createKeyboardInfoMap(pkgID,langID,langName,kbID,kbName,kbVersion,kbIsCustom,font,oskFont);
+
KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, keyboardInfo, result);
}
}
@@ -537,14 +529,152 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
* @param showProgressDialog
*/
public static void download(final Context context, final boolean showProgressDialog) {
- new DownloadTask(context, showProgressDialog).execute();
+ if(USE_DOWNLOAD_MANAGER)
+ downloadKeyboardUsingDownloadManager(context);
+ else
+ new DownloadTask(context, showProgressDialog).execute();
}
public static void download(final Context context, final boolean showProgressDialog,
- final boolean donwloadOnlyLexicalModel) {
- new DownloadTask(context, showProgressDialog, downloadOnlyLexicalModel).execute();
+ final boolean aDownloadOnlyLexicalModel) {
+ if(USE_DOWNLOAD_MANAGER)
+ downloadUsingDownloadManager(context,aDownloadOnlyLexicalModel);
+ else
+ new DownloadTask(context, showProgressDialog, aDownloadOnlyLexicalModel).execute();
}
+ /**
+ * Download in keyboards and lexical model using download manager.
+ * @param context the context
+ * @param aDownloadOnlyLexicalModel is lexical model download
+ */
+ private static void downloadUsingDownloadManager(final Context context,
+ final boolean aDownloadOnlyLexicalModel)
+ {
+ if(aDownloadOnlyLexicalModel)
+ {
+ downloadLexicalModelUsingDownloadManager(context);
+ }
+ else
+ {
+ downloadKeyboardUsingDownloadManager(context);
+ }
+ }
+
+ /**
+ * prepare and execute keyboard download using downloadmanager.
+ * @param context the context
+ */
+ private static void downloadKeyboardUsingDownloadManager(Context context)
+ {
+ if (pkgID == null || pkgID.trim().isEmpty() ||
+ (!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) {
+ throw new IllegalStateException("Invalid keyboard");
+ }
+
+
+
+ List cloudQueries = getPrepareCloudQueriesForKeyboardDownload(context);
+
+ String _downloadid= CloudKeyboardMetaDataDownloadCallback.createDownloadId(langID , kbID);
+
+ if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_downloadid)
+ || CloudDownloadMgr.getInstance().alreadyDownloadingData(
+ CloudKeyboardDataDownloadCallback.createDownloadId(kbID)))
+ {
+ Toast.makeText(context,
+ context.getString(R.string.keyboard_download_is_running_in_background),
+ Toast.LENGTH_SHORT).show();
+ }
+ else
+ {
+ CloudKeyboardMetaDataDownloadCallback _callback = new CloudKeyboardMetaDataDownloadCallback();
+ _callback.setDownloadEventListeners(kbDownloadEventListeners);
+
+ Toast.makeText(context,
+ context.getString(R.string.keyboard_download_start_in_background),
+ Toast.LENGTH_SHORT).show();
+
+ CloudDownloadMgr.getInstance().executeAsDownload(
+ context, _downloadid, null, _callback,
+ cloudQueries.toArray(new CloudApiTypes.CloudApiParam[0]));
+ }
+
+ ((AppCompatActivity) context).finish();
+ }
+
+ /**
+ * Prepare the cloud queries for keyboard metadata download.
+ * @param context the context
+ * @return the result
+ */
+ private static List getPrepareCloudQueriesForKeyboardDownload(Context context)
+ {
+ List cloudQueries = new ArrayList<>();
+
+ String deviceType = CloudDataJsonUtil.getDeviceTypeForCloudQuery(context);
+
+ if (isCustom) {
+ //TODO: will end up in an exception during download???
+ cloudQueries.add(new CloudApiTypes.CloudApiParam(
+ CloudApiTypes.ApiTarget.KeyboardData, url));
+ }
+ else
+ {
+ // Keyman cloud
+ String _remoteUrl = String.format("%s/%s/%s?version=%s&device=%s&languageidtype=bcp47",
+ kKeymanApiBaseURL, langID, kbID, BuildConfig.VERSION_NAME, deviceType);
+ cloudQueries.add(
+ new CloudApiTypes.CloudApiParam(
+ CloudApiTypes.ApiTarget.Keyboard, _remoteUrl)
+ .setType(CloudApiTypes.JSONType.Object)
+ .setAdditionalProperty(CloudKeyboardMetaDataDownloadCallback.PARAM_IS_CUSTOM,isCustom)
+ .setAdditionalProperty(CloudKeyboardMetaDataDownloadCallback.PARAM_LANG_ID,langID)
+ .setAdditionalProperty(CloudKeyboardMetaDataDownloadCallback.PARAM_KB_ID,kbID));
+
+ String _remoteLexicalModelUrl = String.format("%s?q=bcp47:%s", kKeymanApiModelURL, langID);
+ cloudQueries.add(new CloudApiTypes.CloudApiParam(
+ CloudApiTypes.ApiTarget.KeyboardLexicalModels, _remoteLexicalModelUrl)
+ .setType(CloudApiTypes.JSONType.Array));
+ }
+ return cloudQueries;
+ }
+
+ /**
+ * prepare and execute lexical model download using downloadmanager.
+ * @param context the context
+ */
+ private static void downloadLexicalModelUsingDownloadManager(Context context) {
+
+
+ String _downloadid= CloudLexicalPackageDownloadCallback.createDownloadId(modelID);
+
+ if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_downloadid))
+ {
+ Toast.makeText(context,
+ context.getString(R.string.dictionary_download_is_running_in_background),
+ Toast.LENGTH_SHORT).show();
+ }
+ else
+ {
+ CloudLexicalPackageDownloadCallback _callback = new CloudLexicalPackageDownloadCallback();
+ _callback.setDownloadEventListeners(kbDownloadEventListeners);
+
+ CloudApiTypes.CloudApiParam _param = new CloudApiTypes.CloudApiParam(
+ CloudApiTypes.ApiTarget.LexicalModelPackage, url);
+
+ Toast.makeText(context,
+ context.getString(R.string.dictionary_download_start_in_background),
+ Toast.LENGTH_SHORT).show();
+
+ CloudDownloadMgr.getInstance().executeAsDownload(
+ context, _downloadid, null, _callback, _param);
+ }
+
+ ((AppCompatActivity) context).finish();
+ }
+
+
public static boolean isCustom(String u) {
boolean ret = false;
if (u != null && !u.contains(KMKeyboardDownloaderActivity.kKeymanApiBaseURL) &&
@@ -554,108 +684,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
return ret;
}
- // If a font JSONObject contains multiple font font files, only keep the .ttf source
- private static void findTTF(JSONObject jsonFont) {
- boolean updateJsonFont = false;
- try {
- JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource);
- if ((fontSource != null) && hasTTFFont(fontSource)) {
- for (int i = fontSource.length() - 1; i >= 0; i--) {
- String s = fontSource.getString(i);
- if (!FileUtils.isTTFFont(s)) {
- updateJsonFont = true;
- // remove() was added in API 19
- // https://developer.android.com/reference/org/json/JSONArray#remove(int)
- if (Build.VERSION.SDK_INT > 19) {
- fontSource.remove(i);
- } else {
- fontSource = removeJsonObjectAtIndex(fontSource, i);
- }
- }
- }
- if (updateJsonFont) {
- JSONArray copy = fontSource;
- jsonFont.remove(KMManager.KMKey_FontSource);
- jsonFont.put(KMManager.KMKey_FontSource, copy);
- }
- }
- } catch (JSONException e) {
- Log.e(TAG, "findTTF exception" + e);
- }
- }
-
- // Parse the fontSource JSONArray to see if it contains a .ttf font
- private static boolean hasTTFFont(JSONArray fontSource) {
- try {
- for (int i = 0; i < fontSource.length(); i++) {
- String s = fontSource.getString(i);
- if (FileUtils.isTTFFont(s)) {
- return true;
- }
- }
- return false;
- } catch (JSONException e) {
- Log.e(TAG, "hasTTFFont exception" + e);
- return false;
- }
- }
-
- // From https://stackoverflow.com/questions/27427999/remove-jsonobeject-before-android-api-lvl-19
- private static JSONArray removeJsonObjectAtIndex(JSONArray source, int index) throws JSONException {
- if (index < 0 || index > source.length() - 1) {
- throw new IndexOutOfBoundsException();
- }
-
- final JSONArray copy = new JSONArray();
- for (int i=0, count = source.length(); i fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
- if (jsonFont == null)
- return null;
-
- ArrayList urls = new ArrayList();
- JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource);
- if (fontSource != null) {
- int fcCount = fontSource.length();
- for (int i = 0; i < fcCount; i++) {
- String fontSourceString;
- try {
- fontSourceString = fontSource.getString(i);
-
- if (FileUtils.hasFontExtension(fontSourceString)) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) {
- String fontFilename = FileUtils.getSVGFilename(fontSourceString);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
- } else {
- String fontSourceString;
- try {
- fontSourceString = jsonFont.getString(KMManager.KMKey_FontSource);
- if (FileUtils.hasFontExtension(fontSourceString)) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) {
- String fontFilename = FileUtils.getSVGFilename(fontSourceString);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
-
- return urls;
- }
public static void addKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
if (kbDownloadEventListeners == null) {
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardPickerAdapter.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardPickerAdapter.java
index 628e4ef966..848af2b8b0 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardPickerAdapter.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardPickerAdapter.java
@@ -74,8 +74,12 @@ final class KMKeyboardPickerAdapter extends NestedAdapter> keyboardsList = null;
private static ArrayList> lexicalModelsList = null;
@@ -176,6 +179,39 @@ public final class KeyboardPickerActivity extends AppCompatActivity {
int curKbPos = getCurrentKeyboardIndex();
setSelection(curKbPos);
+
+ KMKeyboard.addOnKeyboardEventListener(new KeyboardEventHandler.OnKeyboardEventListener() {
+ @Override
+ public void onKeyboardLoaded(KMManager.KeyboardType keyboardType) {
+
+ }
+
+ @Override
+ public void onKeyboardChanged(String newKeyboard) {
+ int _index = getKeyboardIndex(context,newKeyboard);
+ if(_index>=0)
+ {
+ Map _keyboard = keyboardsList.get(_index);
+ if(_keyboard==null)
+ return;
+ if(_keyboard.get(KMKEY_INTERNAL_NEW_KEYBOARD)==null)
+ return;
+ _keyboard.remove(KMKEY_INTERNAL_NEW_KEYBOARD);
+ saveList(context, KMManager.KMFilename_KeyboardsList);
+ notifyKeyboardsUpdate(context);
+ }
+ }
+
+ @Override
+ public void onKeyboardShown() {
+
+ }
+
+ @Override
+ public void onKeyboardDismissed() {
+
+ }
+ });
}
@Override
@@ -343,9 +379,12 @@ public final class KeyboardPickerActivity extends AppCompatActivity {
if (kbKey.length() >= 3) {
int x = getKeyboardIndex(context, kbKey);
if (x >= 0) {
+ if(keyboardsList.get(x).get(KMKEY_INTERNAL_NEW_KEYBOARD)!=null)
+ keyboardInfo.put(KMKEY_INTERNAL_NEW_KEYBOARD,KMKEY_INTERNAL_NEW_KEYBOARD);
keyboardsList.set(x, keyboardInfo);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
} else {
+ keyboardInfo.put(KMKEY_INTERNAL_NEW_KEYBOARD,KMKEY_INTERNAL_NEW_KEYBOARD);
keyboardsList.add(keyboardInfo);
result = saveList(context, KMManager.KMFilename_KeyboardsList);
if (!result) {
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
index 37ff95d90e..0cc20e4fc3 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
@@ -137,7 +137,9 @@ public final class LanguageListActivity extends AppCompatActivity implements OnK
if (!pkgID.equals(KMManager.KMDefault_UndefinedPackageID)) {
// Custom keyboard already exists in packages/ so just add the language association
KeyboardPickerActivity.addKeyboard(context, kbInfo);
- KMManager.setKeyboard(pkgID, kbID, langID, kbName, language.name, kFont, kOskFont);
+
+ if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
+ KMManager.setKeyboard(pkgID, kbID, langID, kbName, language.name, kFont, kOskFont);
Toast.makeText(context, "Keyboard installed", Toast.LENGTH_SHORT).show();
setResult(RESULT_OK);
((AppCompatActivity) context).finish();
@@ -207,7 +209,8 @@ public final class LanguageListActivity extends AppCompatActivity implements OnK
String kOskFont = keyboardInfo.get(KMManager.KMKey_OskFont);
KeyboardPickerActivity.addKeyboard(this, keyboardInfo);
- KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
+ if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
+ KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
if (result == 2) {
Toast.makeText(context, context.getString(R.string.font_failed_to_download), Toast.LENGTH_LONG).show();
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java
index ef757c6e8e..ad9aca38b4 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java
@@ -53,6 +53,7 @@ public final class LanguagesSettingsActivity extends AppCompatActivity
// ********* ONLY USED BY UPDATE CODE ***********
+ //TODO: Refactoring to separate update logic from view
private static boolean updateCheckFailed = false;
private static boolean updateFailed = false;
private static Calendar lastUpdateCheck = null;
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudApiTypes.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudApiTypes.java
index 7cb8f787bf..ecc758e2eb 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudApiTypes.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudApiTypes.java
@@ -9,8 +9,10 @@ import org.json.JSONObject;
import java.io.File;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
public class CloudApiTypes {
protected static class CloudApiReturns {
@@ -33,25 +35,64 @@ public class CloudApiTypes {
}
- protected enum ApiTarget {
+ public enum ApiTarget {
+ /**
+ * Catalog download: available keyboards including meta data.
+ */
Keyboards,
- LexicalModels
+ /**
+ * Catalog download: available lexical models including meta data.
+ */
+ LexicalModels,
+ /**
+ * Keyboard download: keyboard meta data for the selected keyboard.
+ */
+ Keyboard,
+ /**
+ * Keyboard download: lexical models meta data for the language of the selected keyboard.
+ */
+ KeyboardLexicalModels,
+ /**
+ * Keyboard download: download keyboard data package and fonts.
+ */
+ KeyboardData,
+ /**
+ * Lexical download: download lexical model package
+ * Used for single lexical model download and
+ * automatic lexical model download during keyboard download
+ */
+ LexicalModelPackage,
}
- protected enum JSONType {
+ public enum JSONType {
Array,
Object
}
- protected static class CloudApiParam {
+ public static class CloudApiParam {
public final ApiTarget target;
public final String url;
- public final JSONType type;
+ public JSONType type;
+ private Map additionalProperties = new HashMap<>();
- CloudApiParam(ApiTarget target, String url, JSONType type) {
+ public CloudApiParam(ApiTarget target, String url) {
this.target = target;
this.url = url;
+ }
+
+ public CloudApiParam setType(JSONType type) {
this.type = type;
+ return this;
+ }
+ public CloudApiParam setAdditionalProperty(String aProperty, Object aValue)
+ {
+ additionalProperties.put(aProperty,aValue);
+ return this;
+ }
+
+ public T getAdditionalProperty(String aProperty,Class aType)
+ {
+ return (T)additionalProperties.get(aProperty);
}
}
@@ -61,8 +102,7 @@ public class CloudApiTypes {
private boolean downloadFinished =false;
private long downloadId;
private File destinationFile;
- private CloudApiTypes.JSONType type;
- private CloudApiTypes.ApiTarget target;
+ private CloudApiParam cloudParams;
public SingleCloudDownload(DownloadManager.Request aRequest,File aDestinationFile)
{
@@ -74,13 +114,8 @@ public class CloudApiTypes {
return this;
}
- public SingleCloudDownload setJsonType(JSONType type) {
- this.type = type;
- return this;
- }
-
- public SingleCloudDownload setTarget(ApiTarget target) {
- this.target = target;
+ public SingleCloudDownload setCloudParams(CloudApiParam params) {
+ this.cloudParams = params;
return this;
}
@@ -96,12 +131,8 @@ public class CloudApiTypes {
return destinationFile;
}
- public JSONType getType() {
- return type;
- }
-
- public ApiTarget getTarget() {
- return target;
+ public CloudApiParam getCloudParams() {
+ return cloudParams;
}
}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudCatalogDownloadCallback.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudCatalogDownloadCallback.java
index 8d3e9a2269..cffbd79627 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudCatalogDownloadCallback.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudCatalogDownloadCallback.java
@@ -229,6 +229,11 @@ public class CloudCatalogDownloadCallback implements ICloudDownloadCallback retrievedJSON = new ArrayList<>(aDownload.getSingleDownloads().size());
-
for (CloudApiTypes.SingleCloudDownload _d : aDownload.getSingleDownloads()) {
- JSONParser jsonParser = new JSONParser();
- JSONArray dataArray = null;
- JSONObject dataObject = null;
- if (_d.getDestinationFile() != null && _d.getDestinationFile().length() > 0) {
- try {
+ CloudApiTypes.CloudApiReturns _json_result = CloudDataJsonUtil.retrieveJsonFromDownload(_d);
- if (_d.getType() == CloudApiTypes.JSONType.Array) {
- dataArray = jsonParser.getJSONObjectFromFile(_d.getDestinationFile(),JSONArray.class);
- } else {
- dataObject = jsonParser.getJSONObjectFromFile(_d.getDestinationFile(),JSONObject.class);
- }
- } catch (Exception e) {
- Log.d(TAG, e.getMessage());
- } finally {
- _d.getDestinationFile().delete();
- }
- } else {
- // Offline trouble! That said, we can't get anything, so we simply shouldn't add anything.
- }
-
- if (_d.getType() == CloudApiTypes.JSONType.Array) {
- retrievedJSON.add(new CloudApiTypes.CloudApiReturns(_d.getTarget(), dataArray)); // Null if offline.
- } else {
- retrievedJSON.add(new CloudApiTypes.CloudApiReturns(_d.getTarget(), dataObject)); // Null if offline.
- }
+ if (_json_result!=null)
+ retrievedJSON.add(_json_result); // Null if offline.
}
+
return new CloudCatalogDownloadReturns(retrievedJSON);
}
}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDataJsonUtil.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDataJsonUtil.java
index aabd98061a..3072627d0d 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDataJsonUtil.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDataJsonUtil.java
@@ -1,8 +1,10 @@
package com.tavultesoft.kmea.data;
import android.content.Context;
+import android.os.Build;
import android.util.Log;
+import com.tavultesoft.kmea.JSONParser;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.R;
@@ -29,6 +31,27 @@ public class CloudDataJsonUtil {
{
//no instances
}
+
+ public static HashMap createKeyboardInfoMap(String aPackageId,String aLanguageId, String aLanguageName, String aKeyboardId,
+ String aKeyboardName, String aKeyboardVersion, String anIsCustomKeyboard,
+ String aFont, String aOskFont)
+ {
+ HashMap keyboardInfo = new HashMap();
+ if(aPackageId!=null)
+ keyboardInfo.put(KMManager.KMKey_PackageID, aPackageId);
+ keyboardInfo.put(KMManager.KMKey_KeyboardID, aKeyboardId);
+ keyboardInfo.put(KMManager.KMKey_LanguageID, aLanguageId);
+ keyboardInfo.put(KMManager.KMKey_KeyboardName, aKeyboardName);
+ keyboardInfo.put(KMManager.KMKey_LanguageName, aLanguageName);
+ keyboardInfo.put(KMManager.KMKey_KeyboardVersion, aKeyboardVersion);
+ keyboardInfo.put(KMManager.KMKey_CustomKeyboard, anIsCustomKeyboard);
+ keyboardInfo.put(KMManager.KMKey_Font, aFont);
+ if (aOskFont != null)
+ keyboardInfo.put(KMManager.KMKey_OskFont, aOskFont);
+
+ return keyboardInfo;
+ }
+
static List processKeyboardJSON(JSONObject query, boolean fromKMP) {
List keyboardsList = new ArrayList<>();
//keyboardModifiedDates = new HashMap();
@@ -55,14 +78,8 @@ public class CloudDataJsonUtil {
String kbFont = keyboardJSON.optString(KMManager.KMKey_Font, "");
//String kbKey = String.format("%s_%s", langID, kbID);
- HashMap hashMap = new HashMap();
- hashMap.put(KMManager.KMKey_KeyboardName, kbName);
- hashMap.put(KMManager.KMKey_KeyboardID, kbID);
- hashMap.put(KMManager.KMKey_LanguageName, langName);
- hashMap.put(KMManager.KMKey_LanguageID, langID);
- hashMap.put(KMManager.KMKey_KeyboardVersion, kbVersion);
- hashMap.put(KMManager.KMKey_CustomKeyboard, isCustom);
- hashMap.put(KMManager.KMKey_Font, kbFont);
+ HashMap hashMap = createKeyboardInfoMap(null,langID,langName,kbID,kbName,kbVersion,isCustom,kbFont,null);
+
// if (keyboardModifiedDates.get(kbID) == null) {
// keyboardModifiedDates.put(kbID, keyboardJSON.getString(KMManager.KMKey_KeyboardModified));
@@ -220,4 +237,187 @@ public class CloudDataJsonUtil {
final String jsonLexicalCacheFilename = "jsonLexicalModelsCache.dat";
return new File(context.getCacheDir(), jsonLexicalCacheFilename);
}
+
+ /**
+ * retrieve a json object from a downloaded file.
+ * @param aDownload the download
+ * @return the result
+ */
+ public static CloudApiTypes.CloudApiReturns retrieveJsonFromDownload( CloudApiTypes.SingleCloudDownload aDownload)
+ {
+ JSONParser jsonParser = new JSONParser();
+ JSONArray dataArray = null;
+ JSONObject dataObject = null;
+
+ if (aDownload.getDestinationFile() != null && aDownload.getDestinationFile().length() > 0) {
+ try {
+
+ if (aDownload.getCloudParams().type == CloudApiTypes.JSONType.Array) {
+ dataArray = jsonParser.getJSONObjectFromFile(aDownload.getDestinationFile(),JSONArray.class);
+ } else {
+ dataObject = jsonParser.getJSONObjectFromFile(aDownload.getDestinationFile(),JSONObject.class);
+ }
+ } catch (Exception e) {
+ Log.d(TAG, e.getMessage());
+ } finally {
+ aDownload.getDestinationFile().delete();
+ }
+ } else {
+ // Offline trouble! That said, we can't get anything, so we simply shouldn't add anything.
+ }
+
+ if (aDownload.getCloudParams().type == CloudApiTypes.JSONType.Array)
+ {
+ if(dataArray!=null)
+ return new CloudApiTypes.CloudApiReturns(aDownload.getCloudParams().target, dataArray); // Null if offline.
+ return null;
+ }
+ if(dataObject!=null)
+ return new CloudApiTypes.CloudApiReturns(aDownload.getCloudParams().target, dataObject); // Null if offline.
+
+
+ return null;
+ }
+
+ // If a font JSONObject contains multiple font font files, only keep the .ttf source
+ public static void updateFontSourceToTTFFont(JSONObject jsonFont) {
+ boolean updateJsonFont = false;
+ try {
+ JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource);
+ if ((fontSource != null) && hasTTFFont(fontSource)) {
+ for (int i = fontSource.length() - 1; i >= 0; i--) {
+ String s = fontSource.getString(i);
+ if (!FileUtils.isTTFFont(s)) {
+ updateJsonFont = true;
+ // remove() was added in API 19
+ // https://developer.android.com/reference/org/json/JSONArray#remove(int)
+ if (Build.VERSION.SDK_INT > 19) {
+ fontSource.remove(i);
+ } else {
+ fontSource = removeJsonObjectAtIndex(fontSource, i);
+ }
+ }
+ }
+
+ if (updateJsonFont) {
+ JSONArray copy = fontSource;
+ jsonFont.remove(KMManager.KMKey_FontSource);
+ jsonFont.put(KMManager.KMKey_FontSource, copy);
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "findTTF exception" + e);
+ }
+ }
+
+ // Parse the fontSource JSONArray to see if it contains a .ttf font
+ private static boolean hasTTFFont(JSONArray fontSource) {
+ try {
+ for (int i = 0; i < fontSource.length(); i++) {
+ String s = fontSource.getString(i);
+ if (FileUtils.isTTFFont(s)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (JSONException e) {
+ Log.e(TAG, "hasTTFFont exception" + e);
+ return false;
+ }
+ }
+
+ // From https://stackoverflow.com/questions/27427999/remove-jsonobeject-before-android-api-lvl-19
+ private static JSONArray removeJsonObjectAtIndex(JSONArray source, int index) throws JSONException {
+ if (index < 0 || index > source.length() - 1) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ final JSONArray copy = new JSONArray();
+ for (int i=0, count = source.length(); i fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
+ if (jsonFont == null)
+ return null;
+
+ ArrayList urls = new ArrayList();
+ JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource);
+ if (fontSource != null) {
+ int fcCount = fontSource.length();
+ for (int i = 0; i < fcCount; i++) {
+ String fontSourceString;
+ try {
+ fontSourceString = fontSource.getString(i);
+
+ if (FileUtils.hasFontExtension(fontSourceString)) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) {
+ String fontFilename = FileUtils.getSVGFilename(fontSourceString);
+ urls.add(baseUri + fontFilename);
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+ } else {
+ String fontSourceString;
+ try {
+ fontSourceString = jsonFont.getString(KMManager.KMKey_FontSource);
+ if (FileUtils.hasFontExtension(fontSourceString)) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) {
+ String fontFilename = FileUtils.getSVGFilename(fontSourceString);
+ urls.add(baseUri + fontFilename);
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ return urls;
+ }
+
+ public static JSONObject findMatchingKeyboardByID(JSONArray aKeyboards, String aKbId)
+ throws IllegalStateException, JSONException
+ {
+ if (aKeyboards == null) {
+ throw new IllegalStateException("Keyboard array is empty");
+ }
+
+ JSONObject keyboard = null;
+ // In case keyboards array contains multiple keyboards, get the one with matching keyboard ID
+ for (int index = 0; index < aKeyboards.length(); index++) {
+ keyboard = aKeyboards.getJSONObject(index);
+ if (keyboard != null && (aKbId.equals(keyboard.getString(KMManager.KMKey_ID)))) {
+ break;
+ }
+ }
+ if (keyboard == null) {
+ throw new IllegalStateException("could not find matching keyboard");
+ }
+
+ return keyboard;
+ }
+
+ /**
+ * select device type for api queries.
+ * @param aContext a context
+ * @return the result
+ */
+ public static String getDeviceTypeForCloudQuery(Context aContext)
+ {
+ String deviceType = aContext.getString(R.string.device_type);
+ if (deviceType.equals("AndroidTablet")) {
+ deviceType = "androidtablet";
+ } else {
+ deviceType = "androidphone";
+ }
+ return deviceType;
+ }
+
}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDownloadMgr.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDownloadMgr.java
index 4ddbe25994..321b281131 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDownloadMgr.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudDownloadMgr.java
@@ -189,6 +189,9 @@ public class CloudDownloadMgr{
DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE);
+
+ aCallback.initializeContext(aContext);
+
CloudApiTypes.CloudDownloadSet _downloadSet =
new CloudApiTypes.CloudDownloadSet(
aDownloadIdentifier,aTargetModel);
@@ -232,7 +235,6 @@ public class CloudDownloadMgr{
//.setAllowedOverRoaming(true);// Set if download is allowed on roaming network
return new CloudApiTypes.SingleCloudDownload(_request,_file)
- .setJsonType(aParam.type)
- .setTarget(aParam.target);
+ .setCloudParams(aParam);
}
}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDataDownloadCallback.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDataDownloadCallback.java
new file mode 100644
index 0000000000..36b144d26a
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDataDownloadCallback.java
@@ -0,0 +1,138 @@
+package com.tavultesoft.kmea.data;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.tavultesoft.kmea.KeyboardEventHandler;
+import com.tavultesoft.kmea.R;
+import com.tavultesoft.kmea.util.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static com.tavultesoft.kmea.KMManager.KMDefault_UndefinedPackageID;
+
+/**
+ * Install keyboard data, when download is finished.
+ * Could be keyboard packages or fonts.
+ */
+public class CloudKeyboardDataDownloadCallback implements ICloudDownloadCallback<
+ Void, CloudKeyboardDownloadReturns>
+{
+
+ private static final String TAG = "CloudkbDataDldCb";
+
+ private File dataDir;
+
+ private ArrayList downloadEventListeners = new ArrayList<>();
+ private HashMap keyboardInfo;
+
+ /**
+ * Additional Cloud API parameter:
+ * Parameter to force a special destination file name during installation.
+ * Default name is the file name of the url.
+ */
+ public static final String PARAM_DESTINATION_FILE_NAME = "destination_file_name";
+
+ /**
+ * Additional Cloud API parameter:
+ * Parameter to force a special destination file name during installation.
+ * Default name is the file name of the url.
+ */
+ public static final String PARAM_PACKAGE = "package";
+
+ /**
+ * listeners to inform after installation is completed.
+ * @param aDownloadEventListeners the listeners
+ */
+ public void setDownloadEventListeners(ArrayList aDownloadEventListeners)
+ {
+ downloadEventListeners.clear();
+ downloadEventListeners.addAll(aDownloadEventListeners);
+ }
+
+ /**
+ * Keyboard meta data.
+ * @param aKeyboardInfo the keyboard
+ */
+ public void setKeyboardInfo(HashMap aKeyboardInfo) {
+ this.keyboardInfo = aKeyboardInfo;
+ }
+
+ @Override
+ public void initializeContext(Context context)
+ {
+ dataDir = context.getDir("data", Context.MODE_PRIVATE);
+ }
+
+ @Override
+ public CloudKeyboardDownloadReturns extractCloudResultFromDownloadSet(
+ CloudApiTypes.CloudDownloadSet aDownload)
+ {
+ int _result = FileUtils.DOWNLOAD_SUCCESS;
+ for(CloudApiTypes.SingleCloudDownload _d:aDownload.getSingleDownloads())
+ {
+ if (_d.getDestinationFile() != null && _d.getDestinationFile().length() > 0)
+ {
+
+ try {
+
+ String _pkg = _d.getCloudParams().getAdditionalProperty(PARAM_PACKAGE,String.class);
+ String destination = dataDir.toString() +
+ File.separator + _pkg + File.separator;
+
+ String _filename = _d.getCloudParams().getAdditionalProperty(PARAM_DESTINATION_FILE_NAME,String.class);
+ if (_filename==null) {
+
+ _filename = FileUtils.getFilename(_d.getCloudParams().url);
+ }
+
+ File _data_file = new File(destination,_filename);
+ FileUtils.copy(_d.getDestinationFile(), _data_file);
+
+ }
+ catch (IOException _e)
+ {
+ Log.e(TAG,_e.getLocalizedMessage(),_e);
+ }
+ }
+ else
+ {
+ if (FileUtils.hasFontExtension(_d.getCloudParams().url))
+ _result = 2;
+ else
+ _result = FileUtils.DOWNLOAD_ERROR;
+ }
+ }
+ return new CloudKeyboardDownloadReturns(_result);
+ }
+
+
+
+ @Override
+ public void applyCloudDownloadToModel(Context aContext, Void aModel, CloudKeyboardDownloadReturns aCloudResult)
+ {
+ Toast.makeText(aContext,
+ aContext.getString(R.string.keyboard_download_finished),
+ Toast.LENGTH_SHORT).show();
+
+ if(keyboardInfo!=null)
+ {
+ KeyboardEventHandler.notifyListeners(downloadEventListeners, KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED,
+ keyboardInfo, aCloudResult.kbdResult);
+ }
+ }
+
+ /**
+ * create a download id for the keyboard data.
+ * @param aKeyboardId the keyboard id
+ * @return the result
+ */
+ public static String createDownloadId(String aKeyboardId)
+ {
+ return "keyboarddata_" + aKeyboardId;
+ }
+}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDownloadReturns.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDownloadReturns.java
new file mode 100644
index 0000000000..8aaaa21d84
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudKeyboardDownloadReturns.java
@@ -0,0 +1,21 @@
+package com.tavultesoft.kmea.data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * key board and lexical model download result.
+ */
+public class CloudKeyboardDownloadReturns {
+ public final Integer kbdResult;
+ public final List