Merge pull request #2308 from keymanapp/android-cleanup-downloadtasks

Android cleanup downloadtasks
This commit is contained in:
safolit 2019-11-11 13:39:49 +07:00 committed by GitHub
commit f2192c2bfa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 243 additions and 816 deletions

View file

@ -854,20 +854,12 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi
kbInfo.put(KMManager.KMKey_KeyboardVersion, kbVersion);
kbInfo.put(KMManager.KMKey_Font, kFont);
kbInfo.put(KMManager.KMKey_OskFont, kOskFont);
if (i == 0) {
if (KMManager.addKeyboard(this, kbInfo)) {
if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
KMManager.setKeyboard(packageID, keyboardID, langId, keyboardName, langName, kFont, kOskFont);
}
} else {
KMManager.addKeyboard(this, kbInfo);
}
KMManager.addKeyboard(this, kbInfo);
}
} else {
if (KMManager.addKeyboard(this, keyboardInfo)) {
if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
}
KMManager.addKeyboard(this, keyboardInfo);
}
} else {
// Error notifications handled in LanguageListActivity

View file

@ -1,6 +1,5 @@
package com.tavultesoft.kmea;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@ -8,14 +7,23 @@ import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import java.util.ArrayList;
/**
* Confirmation dialog for downloading or deleting a Keyman keyboard/model
*/
public class ConfirmDialogFragment extends DialogFragment {
public static final String ARG_DIALOG_TYPE = "ConfirmDialogFragment.dialogType";
public static final String ARG_TITLE = "ConfirmDialogFragment.title";
public static final String ARG_MESSAGE = "ConfirmDialogFragment.message";
public static final String ARG_ITEM_KEY = "confirmDialogFragment.itemKey";
private static final String ARG_DIALOG_TYPE = "ConfirmDialogFragment.dialogType";
private static final String ARG_TITLE = "ConfirmDialogFragment.title";
private static final String ARG_MESSAGE = "ConfirmDialogFragment.message";
private static final String ARG_ITEM_KEY = "confirmDialogFragment.itemKey";
private static final String ARG_DOWNLOAD_QUERIES_KEY = "confirmDialogFragment.downloadQueries";
private static final String ARG_MODEL_ID_KEY = "confirmDialogFragment.modelId";
private static final String ARG_LANG_ID_KEY = "confirmDialogFragment.langId";
private static final String ARG_KB_ID_KEY = "confirmDialogFragment.kbId";
private boolean dismissOnSelect = false;
public enum DialogType {
@ -25,17 +33,36 @@ public class ConfirmDialogFragment extends DialogFragment {
DIALOG_TYPE_DELETE_MODEL
}
public static ConfirmDialogFragment newInstance(DialogType dialogType, String title, String message) {
public static ConfirmDialogFragment newInstanceForKeyboard(DialogType dialogType, String title, String message,
String aLangId, String aKbId,
ArrayList<CloudApiTypes.CloudApiParam> aQueries) {
ConfirmDialogFragment frag = new ConfirmDialogFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_DIALOG_TYPE, dialogType);
args.putString(ARG_TITLE, title);
args.putString(ARG_MESSAGE, message);
args.putString(ARG_LANG_ID_KEY, aLangId);
args.putString(ARG_KB_ID_KEY, aKbId);
args.putSerializable(ARG_DOWNLOAD_QUERIES_KEY,aQueries);
frag.setArguments(args);
return frag;
}
public static ConfirmDialogFragment newInstance(DialogType dialogType, String title, String message, String itemKey) {
public static ConfirmDialogFragment newInstanceForLexicalModel(DialogType dialogType, String title, String message,
String aModelId,
ArrayList<CloudApiTypes.CloudApiParam> aQueries) {
ConfirmDialogFragment frag = new ConfirmDialogFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_DIALOG_TYPE, dialogType);
args.putString(ARG_TITLE, title);
args.putString(ARG_MESSAGE, message);
args.putString(ARG_MODEL_ID_KEY, aModelId);
args.putSerializable(ARG_DOWNLOAD_QUERIES_KEY,aQueries);
frag.setArguments(args);
return frag;
}
public static ConfirmDialogFragment newInstanceForItemKeyBasedAction(DialogType dialogType, String title, String message, String itemKey) {
ConfirmDialogFragment frag = new ConfirmDialogFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_DIALOG_TYPE, dialogType);
@ -48,12 +75,17 @@ public class ConfirmDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
super.onCreateDialog(savedInstanceState);
final DialogType dialogType = (DialogType)getArguments().getSerializable(ARG_DIALOG_TYPE);
final String title = getArguments().getString(ARG_TITLE);
final String message = getArguments().getString(ARG_MESSAGE);
final String itemKey = getArguments().getString(ARG_ITEM_KEY);
final String _langId = getArguments().getString(ARG_LANG_ID_KEY);
final String _kbId = getArguments().getString(ARG_KB_ID_KEY);
final String _modelId = getArguments().getString(ARG_MODEL_ID_KEY);
final ArrayList<CloudApiTypes.CloudApiParam> _preparedCloudApiParams =
(ArrayList)getArguments().getSerializable(ARG_DOWNLOAD_QUERIES_KEY);
String positiveLabel = (dialogType == DialogType.DIALOG_TYPE_DOWNLOAD_KEYBOARD ||
dialogType == DialogType.DIALOG_TYPE_DOWNLOAD_MODEL) ?
getString(R.string.label_download) : getString(R.string.label_delete);
@ -66,9 +98,11 @@ public class ConfirmDialogFragment extends DialogFragment {
public void onClick(DialogInterface dialog, int which) {
switch (dialogType) {
case DIALOG_TYPE_DOWNLOAD_KEYBOARD :
// Confirmation to download keyboard
if (KMManager.hasConnection(getActivity())) {
KMKeyboardDownloaderActivity.download(getActivity(), true);
KMKeyboardDownloaderActivity.downloadKeyboard(
getActivity(), _langId, _kbId,_preparedCloudApiParams);
} else {
Toast.makeText(getActivity(), "No internet connection", Toast.LENGTH_SHORT).show();
}
@ -82,7 +116,8 @@ public class ConfirmDialogFragment extends DialogFragment {
case DIALOG_TYPE_DOWNLOAD_MODEL :
// Confirmation to download lexical model
if (KMManager.hasConnection(getActivity())) {
KMKeyboardDownloaderActivity.download(getActivity(), true, true);
KMKeyboardDownloaderActivity.downloadLexicalModel(getActivity(),
_modelId, _preparedCloudApiParams);
} else {
Toast.makeText(getActivity(), "No internet connection", Toast.LENGTH_SHORT).show();
}

View file

@ -1,46 +1,29 @@
package com.tavultesoft.kmea;
import android.os.Build;
import androidx.appcompat.app.AppCompatActivity;
import android.app.DialogFragment;
import android.app.ProgressDialog;
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;
import org.json.JSONObject;
import androidx.appcompat.app.AppCompatActivity;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
import com.tavultesoft.kmea.cloud.impl.CloudKeyboardDataDownloadCallback;
import com.tavultesoft.kmea.cloud.impl.CloudKeyboardMetaDataDownloadCallback;
import com.tavultesoft.kmea.cloud.impl.CloudLexicalPackageDownloadCallback;
import java.io.File;
import java.util.ArrayList;
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;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DOWNLOAD_KEYBOARD;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DOWNLOAD_MODEL;
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";
@ -77,23 +60,24 @@ 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;
private static String modelID;
private static String modelName;
private static String kbName;
private static String langName;
private static Boolean isCustom;
private static Boolean downloadOnlyLexicalModel;
//TODO: use keyboard model class
private String pkgID;
private String kbID;
private String langID;
private String modelID;
private String modelName;
private String kbName;
private String langName;
private Boolean isCustom;
private Boolean downloadOnlyLexicalModel;
private static String customKeyboard;
private static String customLanguage;
private static Boolean isDirect;
private static String url;
private static String filename;
private String customKeyboard;
private String customLanguage;
private Boolean isDirect;
private String url;
private String filename;
//TODO: move to keyboard manager class
private static ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> kbDownloadEventListeners = null;
@Override
@ -136,7 +120,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
}
}
String title = "";
String title;
if (url != null) {
title = String.format("%s: %s", getString(R.string.custom_keyboard), filename);
} else if (customKeyboard != null && customLanguage != null &&
@ -158,429 +142,47 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
DialogFragment dialog;
if (downloadOnlyLexicalModel) {
title = String.format("%s: %s", langName, modelName);
dialog = ConfirmDialogFragment.newInstance(
DIALOG_TYPE_DOWNLOAD_MODEL, title, getString(R.string.confirm_download_model));
dialog = ConfirmDialogFragment.newInstanceForLexicalModel(
DIALOG_TYPE_DOWNLOAD_MODEL, title, getString(R.string.confirm_download_model),
modelID,
prepareCloudApiParamsForLexicalModelDownload());
} else {
dialog = ConfirmDialogFragment.newInstance(
DIALOG_TYPE_DOWNLOAD_KEYBOARD, title, getString(R.string.confirm_download_keyboard));
dialog = ConfirmDialogFragment.newInstanceForKeyboard(
DIALOG_TYPE_DOWNLOAD_KEYBOARD, title, getString(R.string.confirm_download_keyboard),
langID, kbID,
prepareCloudApiParamsForKeyboardDownload());
}
dialog.show(getFragmentManager(), "dialog");
}
/**
* Used by the <code>download</code> method to handle asynchronous downloading and evaluation of
* packages and keyboards.
* prepare cloud api params for lexical models download.
* @return the result
*/
@Deprecated
static class DownloadTask extends AsyncTask<Void, Integer, DownloadTask.Result> {
static class Result {
public final Integer kbdResult;
public final List<Map<String, String>> installedLexicalModels;
public Result(Integer i) {
this(i, null);
}
public Result(Integer i, List<Map<String, String>> installedLexicalModels) {
this.kbdResult = i;
this.installedLexicalModels = installedLexicalModels;
}
}
private ProgressDialog progressDialog;
private String kbVersion = "1.0";
private String kbIsCustom = isCustom ? "Y" : "N";
private String font = "";
private String oskFont = "";
private Context context;
private boolean showProgressDialog;
private boolean downloadOnlyLexicalModel;
public DownloadTask(Context context, boolean showProgressDialog) {
this.context = context;
this.showProgressDialog = showProgressDialog;
this.downloadOnlyLexicalModel = false;
}
public DownloadTask(Context context, boolean showProgressDialog, boolean downloadOnlyLexicalModel) {
this.context = context;
this.showProgressDialog = showProgressDialog;
this.downloadOnlyLexicalModel = downloadOnlyLexicalModel;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (showProgressDialog) {
progressDialog = new ProgressDialog(context);
if (!downloadOnlyLexicalModel) {
progressDialog.setMessage(context.getString(R.string.downloading_keyboard));
} else {
progressDialog.setMessage(context.getString(R.string.downloading_model));
}
progressDialog.setCancelable(false);
if (!((AppCompatActivity) context).isFinishing()) {
progressDialog.show();
} else {
cancel(true);
progressDialog = null;
}
}
}
@Override
protected Result doInBackground(Void... voids) {
int ret = -1;
if (isCancelled())
return new Result(-1);
try {
if (downloadOnlyLexicalModel) {
return downloadKMPLexicalModel();
}
String exceptionStr = "Invalid keyboard";
if (pkgID == null || pkgID.trim().isEmpty() ||
(!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) {
throw new Exception(exceptionStr);
}
String deviceType = context.getString(R.string.device_type);
if (deviceType.equals("AndroidTablet")) {
deviceType = "androidtablet";
} else {
deviceType = "androidphone";
}
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
remoteUrl = String.format("%s/%s/%s?version=%s&device=%s&languageidtype=bcp47",
kKeymanApiBaseURL, langID, kbID, BuildConfig.VERSION_NAME, deviceType);
remoteLexicalModelUrl = String.format("%s?q=bcp47:%s", kKeymanApiModelURL, langID);
}
return downloadNonKMPKeyboard(remoteUrl, remoteLexicalModelUrl);
} catch (Exception e) {
ret = -1;
Log.e(TAG, "Error: " + e, e);
}
return new Result(ret);
}
@Override
protected void onProgressUpdate(Integer... progress) {
// Do nothing
}
@Override
protected void onPostExecute(Result result) {
try {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
progressDialog = null;
}
} catch (Exception e) {
progressDialog = null;
}
((AppCompatActivity) context).finish();
if (!downloadOnlyLexicalModel) {
notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result.kbdResult);
}
if(result.installedLexicalModels != null) {
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
result.installedLexicalModels, 1);
}
}
/**
* Download a KMP Keyman lexical model from Keyman cloud
* @return A Result object with a success code and any successfully downloaded lexical models.
* @throws Exception
*/
protected Result downloadKMPLexicalModel() throws Exception {
int result = -1;
File resourceRoot = new File(context.getDir("data", Context.MODE_PRIVATE).toString() + File.separator);
LexicalModelPackageProcessor kmpProcessor = new LexicalModelPackageProcessor(resourceRoot);
List<Map<String, String>> installedLexicalModels = null;
if (downloadOnlyLexicalModel) {
String destination = (context.getCacheDir() + File.separator).toString();
filename = FileUtils.getFilename(url);
result = FileUtils.download(context, url, destination, filename);
if (result > 0 && FileUtils.hasKeymanPackageExtension(url)) {
// Extract the kmp. Validate it contains only lexical models, and then process the lexical model package
File kmpFile = new File(context.getCacheDir(), filename);
String pkgTarget = kmpProcessor.getPackageTarget(kmpFile);
if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) {
File unzipPath = kmpProcessor.unzipKMP(kmpFile);
installedLexicalModels = kmpProcessor.processKMP(kmpFile, unzipPath, PackageProcessor.PP_LEXICAL_MODELS_KEY);
}
}
}
// We'll notify any listeners of the successful download in onPostExecute.
return new Result(result, installedLexicalModels);
}
/**
* Download a non-KMP Keyman keyboard from Keyman cloud via JSON.
* If an associated lexical model keyboard package is available, download the first one.
* @param remoteUrl String
* @param remoteLexicalModelUrl String API query for lexical model associated with the language ID
* @return ret int -1 for fail; >0 for success; 2 for keyboard downloading but not font
* @throws Exception
*/
protected Result downloadNonKMPKeyboard(String remoteUrl, String remoteLexicalModelUrl) throws Exception {
int ret = -1;
JSONParser jsonParser = new JSONParser();
JSONObject kbData = jsonParser.getJSONObjectFromUrl(remoteUrl);
String exceptionStr = "Could not reach server";
if (kbData == null) {
throw new Exception(exceptionStr);
}
if (isCustom) {
exceptionStr = "Cannot download custom non-KMP keyboard";
throw new Exception(exceptionStr);
}
exceptionStr = "JSON file does not contain a valid \"options\" object";
JSONObject options = kbData.optJSONObject(KMKey_Options);
if (options == null) {
throw new Exception(exceptionStr);
}
String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, "");
if (kbBaseUri.isEmpty()) {
throw new Exception(exceptionStr);
}
String fontBaseUri = options.optString(KMKey_FontBaseURI, "");
// Keyman cloud keyboard distribution via JSON
JSONObject language = kbData.optJSONObject(KMKey_Language);
if (language == null) {
throw new Exception(exceptionStr);
}
langName = language.optString(KMManager.KMKey_Name, "");
JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards);
JSONObject keyboard = CloudDataJsonUtil.findMatchingKeyboardByID(keyboards,kbID);
kbID = keyboard.getString(KMManager.KMKey_ID);
pkgID = keyboard.optString(KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID);
kbName = keyboard.optString(KMManager.KMKey_Name, "");
kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0");
String kbFilename = keyboard.optString(KMKey_Filename, "");
if (kbName.isEmpty() || langName.isEmpty() || kbFilename.isEmpty())
throw new Exception(exceptionStr);
String kbUrl = kbBaseUri + kbFilename;
ArrayList<String> urls = new ArrayList<String>();
urls.add(kbUrl);
JSONObject jsonFont = keyboard.optJSONObject(KMManager.KMKey_Font);
JSONObject jsonOskFont = keyboard.optJSONObject(KMManager.KMKey_OskFont);
if (jsonFont != null) {
CloudDataJsonUtil.updateFontSourceToTTFFont(jsonFont);
}
if (jsonOskFont != null) {
CloudDataJsonUtil.updateFontSourceToTTFFont(jsonOskFont);
}
ArrayList<String> fontUrls = CloudDataJsonUtil.fontUrls(jsonFont, fontBaseUri, false);
ArrayList<String> oskFontUrls = CloudDataJsonUtil.fontUrls(jsonOskFont, fontBaseUri, true);
if (fontUrls != null)
urls.addAll(fontUrls);
if (oskFontUrls != null) {
for (String url : oskFontUrls) {
if (!urls.contains(url))
urls.add(url);
}
}
font = keyboard.optString(KMManager.KMKey_Font);
oskFont = keyboard.optString(KMManager.KMKey_OskFont);
// Also download first associated lexical model if it exists
JSONArray lmData = jsonParser.getJSONObjectFromUrl(remoteLexicalModelUrl, JSONArray.class);
if (lmData != null && lmData.length() > 0) {
try {
JSONObject modelInfo = lmData.getJSONObject(0);
if (modelInfo.has("packageFilename")) {
// TODO: Confirm if user wants to overwrite exisiting model version
urls.add(modelInfo.getString("packageFilename"));
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing lexical model from api.keyman.com. " + e);
}
}
notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_STARTED, 0);
String destination = context.getDir("data", Context.MODE_PRIVATE).toString() +
File.separator + KMDefault_UndefinedPackageID + File.separator + File.separator;
ret = 1;
int result = 0;
File resourceRoot = new File(context.getDir("data", Context.MODE_PRIVATE).toString() + File.separator);
LexicalModelPackageProcessor kmpProcessor = new LexicalModelPackageProcessor(resourceRoot);
List<Map<String, String>> installedLexicalModels = new ArrayList<>();
for (String url : urls) {
String filename = "";
if (FileUtils.hasJavaScriptExtension(url)) {
int start = kbFilename.lastIndexOf("/");
if (start < 0) {
start = 0;
} else {
start++;
}
if (!kbFilename.contains("-")) {
filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js";
} else {
filename = kbFilename.substring(start);
}
} else if (FileUtils.hasKeymanPackageExtension(url)) {
// Save the kmp file in the app cache
destination = (context.getCacheDir() + File.separator).toString();
filename = FileUtils.getFilename(url);
}
result = FileUtils.download(context, url, destination, filename);
if (result > 0 && FileUtils.hasKeymanPackageExtension(url)) {
// Extract the kmp. Validate it contains only lexical models, and then process the lexical model package
File kmpFile = new File(context.getCacheDir(), filename);
String pkgTarget = kmpProcessor.getPackageTarget(kmpFile);
if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) {
File unzipPath = kmpProcessor.unzipKMP(kmpFile);
// Assumption: only one lexical model KMP will occur per download.
installedLexicalModels = kmpProcessor.processKMP(kmpFile, unzipPath, PackageProcessor.PP_LEXICAL_MODELS_KEY);
}
}
if (result < 0) {
if (FileUtils.hasFontExtension(url)) {
// Propagate warning about font failing to download
ret = 2;
} else {
ret = -1;
break;
}
}
}
if (installedLexicalModels.size() != 0) {
// Let the postExecute method signal the listeners - it's triggered on the main thread.
return new Result(ret, installedLexicalModels);
}
return new Result(ret);
}
/**
* Notify listeners when an event happens
* @param eventType
* @param result
*/
protected void notifyListeners(KeyboardEventHandler.EventType eventType, int result) {
if (kbDownloadEventListeners != null) {
HashMap<String, String> keyboardInfo =
CloudDataJsonUtil.createKeyboardInfoMap(pkgID,langID,langName,kbID,kbName,kbVersion,kbIsCustom,font,oskFont);
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);
}
}
}
/**
* Async task to download a Keyman keyboard from either Keyman cloud server or custom url
* @param context
* @param showProgressDialog
*/
public static void download(final Context context, final boolean showProgressDialog) {
if(USE_DOWNLOAD_MANAGER)
downloadKeyboardUsingDownloadManager(context);
else
new DownloadTask(context, showProgressDialog).execute();
}
public static void download(final Context context, final boolean showProgressDialog,
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);
}
private ArrayList<CloudApiTypes.CloudApiParam> prepareCloudApiParamsForLexicalModelDownload() {
ArrayList<CloudApiTypes.CloudApiParam> _params = new ArrayList<>();
_params.add(new CloudApiTypes.CloudApiParam(
CloudApiTypes.ApiTarget.LexicalModelPackage, url));
return _params;
}
/**
* prepare and execute keyboard download using downloadmanager.
* @param context the context
* @param aLangId the language id
* @param aKbId the keyman keyboard id
* @param aPreparedCloudApiParams the prepared api params
*/
private static void downloadKeyboardUsingDownloadManager(Context context)
public static void downloadKeyboard(Context context,
String aLangId, String aKbId,
List<CloudApiTypes.CloudApiParam> aPreparedCloudApiParams)
{
if (pkgID == null || pkgID.trim().isEmpty() ||
(!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) {
throw new IllegalStateException("Invalid keyboard");
}
List<CloudApiTypes.CloudApiParam> cloudQueries = getPrepareCloudQueriesForKeyboardDownload(context);
String _downloadid= CloudKeyboardMetaDataDownloadCallback.createDownloadId(langID , kbID);
String _downloadid= CloudKeyboardMetaDataDownloadCallback.createDownloadId(aLangId , aKbId);
if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_downloadid)
|| CloudDownloadMgr.getInstance().alreadyDownloadingData(
CloudKeyboardDataDownloadCallback.createDownloadId(kbID)))
CloudKeyboardDataDownloadCallback.createDownloadId(aKbId)))
{
Toast.makeText(context,
context.getString(R.string.keyboard_download_is_running_in_background),
@ -589,7 +191,6 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
else
{
CloudKeyboardMetaDataDownloadCallback _callback = new CloudKeyboardMetaDataDownloadCallback();
_callback.setDownloadEventListeners(kbDownloadEventListeners);
Toast.makeText(context,
context.getString(R.string.keyboard_download_start_in_background),
@ -597,22 +198,26 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
CloudDownloadMgr.getInstance().executeAsDownload(
context, _downloadid, null, _callback,
cloudQueries.toArray(new CloudApiTypes.CloudApiParam[0]));
aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0]));
}
((AppCompatActivity) context).finish();
}
/**
* Prepare the cloud queries for keyboard metadata download.
* @param context the context
* Prepare the cloud api params for keyboard metadata download.
* @return the result
*/
private static List<CloudApiTypes.CloudApiParam> getPrepareCloudQueriesForKeyboardDownload(Context context)
private ArrayList<CloudApiTypes.CloudApiParam> prepareCloudApiParamsForKeyboardDownload()
{
List<CloudApiTypes.CloudApiParam> cloudQueries = new ArrayList<>();
if (pkgID == null || pkgID.trim().isEmpty() ||
(!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) {
throw new IllegalStateException("Invalid keyboard");
}
String deviceType = CloudDataJsonUtil.getDeviceTypeForCloudQuery(context);
ArrayList<CloudApiTypes.CloudApiParam> cloudQueries = new ArrayList<>();
String deviceType = CloudDataJsonUtil.getDeviceTypeForCloudQuery(this);
if (isCustom) {
//TODO: will end up in an exception during download???
@ -643,11 +248,15 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
/**
* prepare and execute lexical model download using downloadmanager.
* @param context the context
* @param aModelId the lexical model id
* @param aPreparedCloudApiParams the prepared api params
*/
private static void downloadLexicalModelUsingDownloadManager(Context context) {
public static void downloadLexicalModel(Context context,
String aModelId,
List<CloudApiTypes.CloudApiParam> aPreparedCloudApiParams) {
String _downloadid= CloudLexicalPackageDownloadCallback.createDownloadId(modelID);
String _downloadid= CloudLexicalPackageDownloadCallback.createDownloadId(aModelId);
if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_downloadid))
{
@ -658,17 +267,14 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
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);
context, _downloadid, null, _callback,
aPreparedCloudApiParams.toArray(new CloudApiTypes.CloudApiParam[0]));
}
((AppCompatActivity) context).finish();
@ -688,7 +294,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
public static void addKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
if (kbDownloadEventListeners == null) {
kbDownloadEventListeners = new ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener>();
kbDownloadEventListeners = new ArrayList<>();
}
if (listener != null && !kbDownloadEventListeners.contains(listener)) {
@ -701,4 +307,8 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
kbDownloadEventListeners.remove(listener);
}
}
public static ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> getKbDownloadEventListeners() {
return kbDownloadEventListeners;
}
}

View file

@ -53,8 +53,8 @@ import com.google.firebase.analytics.FirebaseAnalytics;
import com.tavultesoft.kmea.KeyboardEventHandler.EventType;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
import com.tavultesoft.kmea.data.CloudDataJsonUtil;
import com.tavultesoft.kmea.data.CloudDownloadMgr;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.packages.JSONUtils;
import com.tavultesoft.kmea.packages.PackageProcessor;

View file

@ -145,8 +145,6 @@ public final class KeyboardListActivity extends AppCompatActivity implements OnK
String kOskFont = keyboardInfo.get(KMManager.KMKey_OskFont);
KeyboardPickerActivity.addKeyboard(this, keyboardInfo);
if (!KMKeyboardDownloaderActivity.USE_DOWNLOAD_MANAGER)
KMManager.setKeyboard(packageID, keyboardID, languageID, keyboardName, languageName, kFont, kOskFont);
}
finish();
}

View file

@ -14,7 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tavultesoft.kmea.data.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.Keyboard;

View file

@ -1,20 +1,17 @@
/**
/*
* Copyright (C) 2019 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
@ -39,15 +36,11 @@ import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_
// Public access is necessary to avoid IllegalAccessException
public final class KeyboardSettingsActivity extends AppCompatActivity {
private static final String TAG = "KbSettingsActivity";
private static Toolbar toolbar = null;
private static ListView listView = null;
private DialogFragment dialog;
private static ArrayList<HashMap<String, String>> infoList = null;
protected static Typeface titleFont = null;
private final String titleKey = "title";
private final String subtitleKey = "subtitle";
private final String iconKey = "icon";
private static Typeface titleFont = null;
private static final String titleKey = "title";
private static final String subtitleKey = "subtitle";
private static final String iconKey = "icon";
@Override
public void onCreate(Bundle savedInstanceState) {
@ -57,13 +50,13 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
final String authority = FileProviderUtils.getAuthority(context);
setContentView(R.layout.activity_list_layout);
toolbar = (Toolbar) findViewById(R.id.list_toolbar);
final Toolbar toolbar = findViewById(R.id.list_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
listView = (ListView) findViewById(R.id.listView);
final ListView listView = findViewById(R.id.listView);
final String packageID = getIntent().getStringExtra(KMManager.KMKey_PackageID);
final String languageID = getIntent().getStringExtra(KMManager.KMKey_LanguageID);
@ -72,22 +65,22 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
final String kbName = getIntent().getStringExtra(KMManager.KMKey_KeyboardName);
final String kbVersion = getIntent().getStringExtra(KMManager.KMKey_KeyboardVersion);
final TextView textView = (TextView) findViewById(R.id.bar_title);
final TextView textView = findViewById(R.id.bar_title);
textView.setText(kbName);
if (titleFont != null)
textView.setTypeface(titleFont, Typeface.BOLD);
infoList = new ArrayList<HashMap<String, String>>();
infoList = new ArrayList<>();
// Display keyboard version title
final String noIcon = "0";
HashMap<String, String> hashMap = new HashMap<String, String>();
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(titleKey, getString(R.string.keyboard_version));
hashMap.put(subtitleKey, kbVersion);
hashMap.put(iconKey, noIcon);
infoList.add(hashMap);
// Display keyboard help link
hashMap = new HashMap<String, String>();
hashMap = new HashMap<>();
final String helpUrlStr = getIntent().getStringExtra(KMManager.KMKey_HelpLink);
final String customHelpLink = getIntent().getStringExtra(KMManager.KMKey_CustomHelpLink);
// Check if app declared FileProvider
@ -105,7 +98,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
// Display uninstall keyboard
if (!packageID.equalsIgnoreCase(KMManager.KMDefault_UndefinedPackageID) ||
!kbID.equalsIgnoreCase(KMManager.KMDefault_KeyboardID)) {
hashMap = new HashMap<String, String>();
hashMap = new HashMap<>();
hashMap.put(titleKey, getString(R.string.uninstall_keyboard));
hashMap.put(subtitleKey, "");
hashMap.put(iconKey, noIcon);
@ -118,7 +111,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
ListAdapter adapter = new SimpleAdapter(context, infoList, R.layout.list_row_layout2, from, to) {
@Override
public boolean isEnabled(int position) {
HashMap<String, String> hashMap = (HashMap<String, String>)infoList.get(position);
HashMap<String, String> hashMap = infoList.get(position);
String itemTitle = MapCompat.getOrDefault(hashMap, titleKey, "");
String icon = MapCompat.getOrDefault(hashMap, iconKey, noIcon);
if (itemTitle.equals(getString(R.string.keyboard_version))) {
@ -174,7 +167,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
// Uninstall selected keyboard
String title = String.format("%s: %s", languageName, kbName);
String keyboardKey = String.format("%s_%s", languageID, kbID);
DialogFragment dialog = ConfirmDialogFragment.newInstance(
DialogFragment dialog = ConfirmDialogFragment.newInstanceForItemKeyBasedAction(
DIALOG_TYPE_DELETE_KEYBOARD, title, getString(R.string.confirm_delete_keyboard), keyboardKey);
dialog.show(getFragmentManager(), "dialog");
}
@ -191,9 +184,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
@Override
public void onDestroy(){
super.onDestroy();
if ( dialog !=null ){
dialog.dismiss();
}
}
}

View file

@ -4,9 +4,6 @@
package com.tavultesoft.kmea;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -15,7 +12,7 @@ import org.json.JSONArray;
import org.json.JSONObject;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener;
import com.tavultesoft.kmea.data.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.Keyboard;
@ -138,8 +135,6 @@ public final class LanguageListActivity extends AppCompatActivity implements OnK
// Custom keyboard already exists in packages/ so just add the language association
KeyboardPickerActivity.addKeyboard(context, kbInfo);
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();
@ -209,8 +204,6 @@ public final class LanguageListActivity extends AppCompatActivity implements OnK
String kOskFont = keyboardInfo.get(KMManager.KMKey_OskFont);
KeyboardPickerActivity.addKeyboard(this, keyboardInfo);
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();

View file

@ -27,7 +27,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.tavultesoft.kmea.data.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.adapters.AdapterFilter;

View file

@ -1,7 +1,6 @@
/**
/*
* Copyright (C) 2017 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
import java.io.File;
@ -18,14 +17,11 @@ import android.net.Uri;
import android.os.Bundle;
import androidx.core.content.FileProvider;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
@ -39,10 +35,7 @@ import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_
// Public access is necessary to avoid IllegalAccessException
public final class ModelInfoActivity extends AppCompatActivity {
private static Toolbar toolbar = null;
private static ListView listView = null;
private static ArrayList<HashMap<String, String>> infoList = null;
protected static Typeface titleFont = null;
private final String titleKey = "title";
private final String subtitleKey = "subtitle";
private final String iconKey = "icon";
@ -55,13 +48,13 @@ public final class ModelInfoActivity extends AppCompatActivity {
final String authority = FileProviderUtils.getAuthority(context);
setContentView(R.layout.activity_list_layout);
toolbar = (Toolbar) findViewById(R.id.list_toolbar);
final Toolbar toolbar = findViewById(R.id.list_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
listView = (ListView) findViewById(R.id.listView);
final ListView listView = findViewById(R.id.listView);
final String packageID = getIntent().getStringExtra(KMManager.KMKey_PackageID);
final String languageID = getIntent().getStringExtra(KMManager.KMKey_LanguageID);
@ -69,23 +62,20 @@ public final class ModelInfoActivity extends AppCompatActivity {
final String modelName = getIntent().getStringExtra(KMManager.KMKey_LexicalModelName);
final String modelVersion = getIntent().getStringExtra(KMManager.KMKey_LexicalModelVersion);
final TextView textView = (TextView) findViewById(R.id.bar_title);
final TextView textView = findViewById(R.id.bar_title);
textView.setText(String.format(getString(R.string.model_info_header), modelName));
if (titleFont != null)
textView.setTypeface(titleFont, Typeface.BOLD);
infoList = new ArrayList<HashMap<String, String>>();
infoList = new ArrayList<>();
// Display model version title
final String noIcon = "0";
HashMap<String, String> hashMap = new HashMap<String, String>();
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(titleKey, getString(R.string.model_version));
hashMap.put(subtitleKey, modelVersion);
hashMap.put(iconKey, noIcon);
infoList.add(hashMap);
// Display model help link
hashMap = new HashMap<String, String>();
hashMap = new HashMap<>();
final String customHelpLink = getIntent().getStringExtra(KMManager.KMKey_CustomHelpLink);
// Check if app declared FileProvider
// Currently, model help only available if custom link exists
@ -102,7 +92,7 @@ public final class ModelInfoActivity extends AppCompatActivity {
infoList.add(hashMap);
// Display link to uninstall model
hashMap = new HashMap<String, String>();
hashMap = new HashMap<>();
hashMap.put(titleKey, getString(R.string.uninstall_model));
hashMap.put(subtitleKey, "");
hashMap.put(iconKey, noIcon);
@ -114,7 +104,7 @@ public final class ModelInfoActivity extends AppCompatActivity {
ListAdapter adapter = new SimpleAdapter(context, infoList, R.layout.list_row_layout2, from, to) {
@Override
public boolean isEnabled(int position) {
HashMap<String, String> hashMap = (HashMap<String, String>)infoList.get(position);
HashMap<String, String> hashMap = infoList.get(position);
String itemTitle = MapCompat.getOrDefault(hashMap, titleKey, "");
String icon = MapCompat.getOrDefault(hashMap, iconKey, noIcon);
if (itemTitle.equals(getString(R.string.model_version))) {
@ -168,7 +158,7 @@ public final class ModelInfoActivity extends AppCompatActivity {
} else if (itemTitle.equals(getString(R.string.uninstall_model))) {
// Uninstall selected model
String lexicalModelKey = String.format("%s_%s_%s", packageID, languageID, modelID);
DialogFragment dialog = ConfirmDialogFragment.newInstance(
DialogFragment dialog = ConfirmDialogFragment.newInstanceForItemKeyBasedAction(
DIALOG_TYPE_DELETE_MODEL, modelName, getString(R.string.confirm_delete_model), lexicalModelKey);
dialog.show(getFragmentManager(), "dialog");

View file

@ -1,4 +1,4 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud;
import android.app.DownloadManager;
@ -8,14 +8,14 @@ import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.Serializable;
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 {
public static class CloudApiReturns {
public final ApiTarget target;
public final JSONArray jsonArray;
@ -69,11 +69,14 @@ public class CloudApiTypes {
Object
}
public static class CloudApiParam {
public static class CloudApiParam implements Serializable {
static final long serialVersionUID = 1L;
public final ApiTarget target;
public final String url;
public JSONType type;
private Map<String,Object> additionalProperties = new HashMap<>();
private final HashMap<String,Serializable> additionalProperties = new HashMap<>();
public CloudApiParam(ApiTarget target, String url) {
this.target = target;
@ -84,24 +87,24 @@ public class CloudApiTypes {
this.type = type;
return this;
}
public CloudApiParam setAdditionalProperty(String aProperty, Object aValue)
public CloudApiParam setAdditionalProperty(String aProperty, Serializable aValue)
{
additionalProperties.put(aProperty,aValue);
return this;
}
public <T> T getAdditionalProperty(String aProperty,Class<T> aType)
public <T extends Serializable> T getAdditionalProperty(String aProperty,Class<T> aType)
{
return (T)additionalProperties.get(aProperty);
return aType.cast(additionalProperties.get(aProperty));
}
}
public static class SingleCloudDownload
{
private DownloadManager.Request request;
private final DownloadManager.Request request;
private boolean downloadFinished =false;
private long downloadId;
private File destinationFile;
private final File destinationFile;
private CloudApiParam cloudParams;
public SingleCloudDownload(DownloadManager.Request aRequest,File aDestinationFile)
@ -142,9 +145,9 @@ public class CloudApiTypes {
* @param <ResultType> the result type of the download
*/
public static class CloudDownloadSet<ModelType,ResultType> {
private String downloadIdentifier;
private ModelType targetModel;
private LinkedList<SingleCloudDownload> downloads = new LinkedList<>();
private final String downloadIdentifier;
private final ModelType targetModel;
private final LinkedList<SingleCloudDownload> downloads = new LinkedList<>();
private ICloudDownloadCallback<ModelType,ResultType> callback;
@ -213,7 +216,6 @@ public class CloudApiTypes {
return;
}
}
return;
}

View file

@ -1,4 +1,4 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud;
import android.content.Context;
import android.os.Build;
@ -8,6 +8,9 @@ import com.tavultesoft.kmea.JSONParser;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.data.LexicalModel;
import com.tavultesoft.kmea.util.FileUtils;
import org.json.JSONArray;
@ -52,7 +55,7 @@ public class CloudDataJsonUtil {
return keyboardInfo;
}
static List<Keyboard> processKeyboardJSON(JSONObject query, boolean fromKMP) {
public static List<Keyboard> processKeyboardJSON(JSONObject query, boolean fromKMP) {
List<Keyboard> keyboardsList = new ArrayList<>();
//keyboardModifiedDates = new HashMap<String, String>();
@ -96,7 +99,7 @@ public class CloudDataJsonUtil {
return keyboardsList;
}
static List<LexicalModel> processLexicalModelJSON(JSONArray models) {
public static List<LexicalModel> processLexicalModelJSON(JSONArray models) {
List<LexicalModel> modelList = new ArrayList<>(models.length());
try {
@ -158,7 +161,7 @@ public class CloudDataJsonUtil {
return modelList;
}
static JSONArray getCachedJSONArray(File file) {
public static JSONArray getCachedJSONArray(File file) {
JSONArray lmData = null;
try {
// Read from cache file
@ -198,7 +201,7 @@ public class CloudDataJsonUtil {
* be used for each API call, such as for keyboards vs lexical models.
* @param json - Array of JSON objects containing API return info
*/
protected static void saveJSONArrayToCache(File file, JSONArray json) {
public static void saveJSONArrayToCache(File file, JSONArray json) {
ObjectOutput objOutput;
try {
// Save to cache file
@ -216,7 +219,7 @@ public class CloudDataJsonUtil {
* be used for each API call, such as for keyboards vs lexical models.
* @param json - JSON object containing API return info
*/
protected static void saveJSONObjectToCache(File file, JSONObject json) {
public static void saveJSONObjectToCache(File file, JSONObject json) {
ObjectOutput objOutput;
try {
// Save to cache file
@ -243,7 +246,7 @@ public class CloudDataJsonUtil {
* @param aDownload the download
* @return the result
*/
public static CloudApiTypes.CloudApiReturns retrieveJsonFromDownload( CloudApiTypes.SingleCloudDownload aDownload)
public static CloudApiTypes.CloudApiReturns retrieveJsonFromDownload(CloudApiTypes.SingleCloudDownload aDownload)
{
JSONParser jsonParser = new JSONParser();
JSONArray dataArray = null;
@ -258,7 +261,7 @@ public class CloudDataJsonUtil {
dataObject = jsonParser.getJSONObjectFromFile(aDownload.getDestinationFile(),JSONObject.class);
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
Log.d(TAG, e.getMessage(),e);
} finally {
aDownload.getDestinationFile().delete();
}

View file

@ -1,4 +1,4 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
@ -126,6 +126,8 @@ public class CloudDownloadMgr{
synchronized (downloadSetByDownloadIdentifier) {
CloudApiTypes.CloudDownloadSet _parentSet = getDownloadSetForInternalDownloadId(anInternalDownloadId);
if(_parentSet==null)
throw new IllegalStateException("Download with ID " + anInternalDownloadId + " is not available");
_parentSet.setDone(anInternalDownloadId);
if(!_parentSet.hasOpenDownloads())
{
@ -188,7 +190,8 @@ public class CloudDownloadMgr{
return;
DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE);
if(downloadManager==null)
throw new IllegalStateException("Downloadmanager is not available");
aCallback.initializeContext(aContext);

View file

@ -1,7 +1,10 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud;
import android.content.Context;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
/**
* Interface for {@link CloudDownloadMgr} as callback to do use case specific task.
* @param <ModelType> the model objects type

View file

@ -1,13 +1,20 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.tavultesoft.kmea.JSONParser;
import com.tavultesoft.kmea.KeyboardPickerActivity;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.ICloudDownloadCallback;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.data.LanguageResource;
import com.tavultesoft.kmea.data.LexicalModel;
import com.tavultesoft.kmea.util.FileUtils;
import org.json.JSONArray;
@ -20,7 +27,7 @@ import java.util.List;
* Callback for cloud catalogue download.
* Is used for download with progress and download with Clientdownloadmanager.
*/
public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Dataset, CloudCatalogDownloadReturns>{
public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Dataset, CloudCatalogDownloadReturns> {
private static final String TAG = "CloudCatalogDownloadCb";
private static final boolean DEBUG_SIMULATE_UPDATES = false;
@ -127,10 +134,6 @@ public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Data
public void processCloudReturns(Dataset aDataSet, CloudCatalogDownloadReturns jsonTuple, boolean executeCallbacks) {
// Only empty if no queries returned data - we're offline.
if (jsonTuple.isEmpty()) {
if (this.updateHandler == null) {
String msg = context.getString(R.string.catalog_unavailable);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
this.failure.run(); // Signal failure to download to our failure callback.
return;
}

View file

@ -1,4 +1,6 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import org.json.JSONArray;
import org.json.JSONObject;
@ -8,7 +10,7 @@ import java.util.List;
/**
* Result type for catalogue download.
*/
class CloudCatalogDownloadReturns {
public class CloudCatalogDownloadReturns {
public JSONObject keyboardJSON;
public JSONArray lexicalModelJSON;

View file

@ -1,20 +1,20 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KeyboardEventHandler;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.ICloudDownloadCallback;
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.
@ -27,7 +27,6 @@ public class CloudKeyboardDataDownloadCallback implements ICloudDownloadCallback
private File dataDir;
private ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> downloadEventListeners = new ArrayList<>();
private HashMap<String,String> keyboardInfo;
/**
@ -44,16 +43,6 @@ public class CloudKeyboardDataDownloadCallback implements ICloudDownloadCallback
*/
public static final String PARAM_PACKAGE = "package";
/**
* listeners to inform after installation is completed.
* @param aDownloadEventListeners the listeners
*/
public void setDownloadEventListeners(ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> aDownloadEventListeners)
{
downloadEventListeners.clear();
downloadEventListeners.addAll(aDownloadEventListeners);
}
/**
* Keyboard meta data.
* @param aKeyboardInfo the keyboard
@ -121,7 +110,8 @@ public class CloudKeyboardDataDownloadCallback implements ICloudDownloadCallback
if(keyboardInfo!=null)
{
KeyboardEventHandler.notifyListeners(downloadEventListeners, KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED,
KeyboardEventHandler.notifyListeners(KMKeyboardDownloaderActivity.getKbDownloadEventListeners(),
KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED,
keyboardInfo, aCloudResult.kbdResult);
}
}

View file

@ -1,4 +1,4 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import java.util.List;
import java.util.Map;

View file

@ -1,4 +1,4 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import android.content.Context;
import android.util.Log;
@ -8,6 +8,10 @@ import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KeyboardEventHandler;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
import com.tavultesoft.kmea.cloud.ICloudDownloadCallback;
import com.tavultesoft.kmea.util.FileUtils;
import org.json.JSONArray;
@ -27,7 +31,7 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
/**
* the metadata result and all necessary downloads which should be started.
*/
public static class MetaDataResult
static class MetaDataResult
{
CloudApiTypes.CloudApiReturns returnjson;
CloudApiTypes.CloudApiParam params;
@ -38,8 +42,6 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
private static final String TAG = "CloudKeyboardMetaDldCb";
private ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> downloadEventListeners = new ArrayList<>();
/**
* Additional Cloud API parameter: Is custom keyboard.
*/
@ -53,14 +55,7 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
*/
public static final String PARAM_KB_ID = "kb_id";
/**
* Listeners to notify about starting the data download.
* @param aDownloadEventListeners
*/
public void setDownloadEventListeners(ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> aDownloadEventListeners) {
downloadEventListeners.clear();
downloadEventListeners.addAll(aDownloadEventListeners);
}
@Override
public void initializeContext(Context context) {
@ -118,7 +113,6 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
if(_r.returnjson.target== CloudApiTypes.ApiTarget.Keyboard)
{
CloudKeyboardDataDownloadCallback _callback = new CloudKeyboardDataDownloadCallback();
_callback.setDownloadEventListeners(downloadEventListeners);
_callback.setKeyboardInfo(_r.keyboardInfo);
if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_r.additionalDownloadid))
@ -126,7 +120,8 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
continue;
}
KeyboardEventHandler.notifyListeners(downloadEventListeners,
KeyboardEventHandler.notifyListeners(
KMKeyboardDownloaderActivity.getKbDownloadEventListeners(),
KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_STARTED, _r.keyboardInfo, 0);
CloudDownloadMgr.getInstance().executeAsDownload(aContext, _r.additionalDownloadid, null, _callback,
@ -142,7 +137,6 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
continue;
}
CloudLexicalPackageDownloadCallback _callback = new CloudLexicalPackageDownloadCallback();
_callback.setDownloadEventListeners(downloadEventListeners);
Toast.makeText(aContext,
aContext.getString(R.string.dictionary_download_start_in_background),
@ -266,11 +260,12 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
}
if (oskFontUrls != null) {
for (String url : oskFontUrls) {
if (!urls.contains(url))
if (fontUrls==null || ! fontUrls.contains(url)) {
urls.add(new CloudApiTypes.CloudApiParam(CloudApiTypes.ApiTarget.KeyboardData, url)
.setAdditionalProperty(
CloudKeyboardDataDownloadCallback.PARAM_PACKAGE, _pkgID));
;
}
}
}

View file

@ -1,13 +1,14 @@
package com.tavultesoft.kmea.data;
package com.tavultesoft.kmea.cloud.impl;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KeyboardEventHandler;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.ICloudDownloadCallback;
import com.tavultesoft.kmea.packages.LexicalModelPackageProcessor;
import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.util.FileUtils;
@ -16,14 +17,10 @@ import org.json.JSONException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static com.tavultesoft.kmea.KMManager.KMDefault_UndefinedPackageID;
/**
* Install lexical model.
*/
@ -36,18 +33,6 @@ public class CloudLexicalPackageDownloadCallback implements ICloudDownloadCallba
private File resourceRoot;
private File cacheDir;
private ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> downloadEventListeners = new ArrayList<>();
/**
* listeners to inform after installation is completed.
* @param aDownloadEventListeners the listeners
*/
public void setDownloadEventListeners(ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> aDownloadEventListeners)
{
downloadEventListeners.clear();
downloadEventListeners.addAll(aDownloadEventListeners);
}
@Override
public void initializeContext(Context context)
{
@ -108,7 +93,8 @@ public class CloudLexicalPackageDownloadCallback implements ICloudDownloadCallba
if(aCloudResult.installedLexicalModels != null)
{
KeyboardEventHandler.notifyListeners(downloadEventListeners, KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
KeyboardEventHandler.notifyListeners(KMKeyboardDownloaderActivity.getKbDownloadEventListeners(),
KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
aCloudResult.installedLexicalModels, aCloudResult.kbdResult);
}
}

View file

@ -1,161 +0,0 @@
package com.tavultesoft.kmea.data;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.tavultesoft.kmea.JSONParser;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.R;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
class CloudCatalogDownloadTask extends AsyncTask<CloudApiTypes.CloudApiParam, Integer, CloudCatalogDownloadReturns> {
private static final String TAG = "CloudCatDownloadTask";
private final boolean hasConnection;
private ProgressDialog progressDialog;
private final Context context;
private final Dataset dataset;
private final CloudCatalogDownloadCallback callback;
public CloudCatalogDownloadTask(Context context, Dataset dataset, CloudCatalogDownloadCallback aCallback) {
this.context = context;
this.dataset = dataset;
this.hasConnection = KMManager.hasConnection(context);
callback = aCallback;
}
protected void showProgressDialog(final Runnable finishCallback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
progressDialog = new ProgressDialog(context, R.style.AppTheme_Dialog_Progress);
} else {
progressDialog = new ProgressDialog(context);
}
progressDialog.setMessage(context.getString(R.string.getting_cloud_catalog));
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
cancel(true);
progressDialog.dismiss();
progressDialog = null;
if (finishCallback != null) {
finishCallback.run();
}
return;
}
});
progressDialog.setCancelable(true);
if (!((AppCompatActivity) context).isFinishing()) {
progressDialog.show();
} else {
cancel(true);
progressDialog = null;
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (hasConnection) {
showProgressDialog(new Runnable() {
@Override
public void run() { // runs on 'cancel' selection.
callback.handleDownloadError();
}
});
}
}
@Override
protected CloudCatalogDownloadReturns doInBackground(CloudApiTypes.CloudApiParam... params) {
if (isCancelled()) {
return null;
}
List<CloudApiTypes.CloudApiReturns> retrievedJSON = new ArrayList<>(params.length);
if (progressDialog != null) {
progressDialog.setMax(params.length);
}
for (CloudApiTypes.CloudApiParam param : params) {
JSONParser jsonParser = new JSONParser();
JSONArray dataArray = null;
JSONObject dataObject = null;
if (hasConnection) {
try {
String remoteUrl = param.url;
if (param.type == CloudApiTypes.JSONType.Array) {
dataArray = jsonParser.getJSONObjectFromUrl(remoteUrl, JSONArray.class);
} else {
dataObject = jsonParser.getJSONObjectFromUrl(remoteUrl, JSONObject.class);
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
} else {
// Offline trouble! That said, we can't get anything, so we simply shouldn't add anything.
}
if (param.type == CloudApiTypes.JSONType.Array) {
retrievedJSON.add(new CloudApiTypes.CloudApiReturns(param.target, dataArray)); // Null if offline.
} else {
retrievedJSON.add(new CloudApiTypes.CloudApiReturns(param.target, dataObject)); // Null if offline.
}
if (progressDialog != null) {
progressDialog.setProgress(progressDialog.getProgress());
}
}
return new CloudCatalogDownloadReturns(retrievedJSON); // Will report empty arrays/objects if offline.
}
@Override
protected void onPostExecute(CloudCatalogDownloadReturns jsonTuple) {
callback.saveDataToCache(jsonTuple);
if (progressDialog != null && progressDialog.isShowing()) {
try {
progressDialog.dismiss();
progressDialog = null;
} catch (Exception e) {
progressDialog = null;
}
}
callback.ensureInitCloudReturn(context,dataset,jsonTuple);
callback.processCloudReturns(dataset,jsonTuple, true);
}
}

View file

@ -12,6 +12,11 @@ import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KeyboardPickerActivity;
import com.tavultesoft.kmea.R;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.impl.CloudCatalogDownloadCallback;
import com.tavultesoft.kmea.cloud.impl.CloudCatalogDownloadReturns;
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
import com.tavultesoft.kmea.packages.JSONUtils;
import org.json.JSONArray;
@ -28,9 +33,6 @@ public class CloudRepository {
static public final CloudRepository shared = new CloudRepository();
private static final String TAG = "CloudRepository";
//TODO: Should be removed with the old implementation when downloadmanager impl works
public static final boolean USE_DOWNLOAD_MANAGER = true;
public static final String DOWNLOAD_IDENTIFIER_CATALOGUE = "catalogue";
private Dataset memCachedDataset;
@ -165,8 +167,7 @@ public class CloudRepository {
{
preCacheDataSet(context,updateHandler,onSuccess,onFailure);
if(USE_DOWNLOAD_MANAGER)
downloadMetaDataFromServer(context,updateHandler,onSuccess,onFailure);
downloadMetaDataFromServer(context,updateHandler,onSuccess,onFailure);
}
/**
@ -292,7 +293,7 @@ public class CloudRepository {
preCacheDataSet(context,null,null,null);
if(USE_DOWNLOAD_MANAGER && CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
if(CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
String msg = context.getString(R.string.catalog_download_is_running_in_background);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
@ -307,7 +308,6 @@ public class CloudRepository {
* @param updateHandler An object that can handle update notification if desired.
* @param onSuccess A callback to be triggered on completion of all queries and operations.
* @param onFailure A callback to be triggered upon failure of a query.
* @return A Dataset object implementing the Adapter interface to be asynchronously filled.
*/
private void downloadMetaDataFromServer(@NonNull Context context, UpdateHandler updateHandler, Runnable onSuccess, Runnable onFailure) {
boolean loadKeyboardsFromCache = this.shouldUseCache(context, CloudDataJsonUtil.getKeyboardCacheFile(context));
@ -355,23 +355,15 @@ public class CloudRepository {
// We need the array to be exactly the same size as our entry count.
CloudApiTypes.CloudApiParam[] params = new CloudApiTypes.CloudApiParam[cloudQueryEntries];
cloudQueries.toArray(params);
if (USE_DOWNLOAD_MANAGER) {
if (CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
String msg = context.getString(R.string.catalog_download_is_running_in_background);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
} else {
String msg = context.getString(R.string.catalog_download_start_in_background);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
CloudDownloadMgr.getInstance().executeAsDownload(
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
}
if (CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
String msg = context.getString(R.string.catalog_download_is_running_in_background);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
} else {
CloudCatalogDownloadTask downloadTask = new CloudCatalogDownloadTask(context, memCachedDataset, _download_callback);
// We can pass in multiple URLs; this format is extensible if we need extra catalogs in the future.
downloadTask.execute(params);
String msg = context.getString(R.string.catalog_download_start_in_background);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
CloudDownloadMgr.getInstance().executeAsDownload(
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
}
}
}

View file

@ -121,8 +121,8 @@ public class JSONUtils {
/**
* Iterate through a JSONArray to determine if a language/keyboard/model ID exists.
* @param {a} JSONArray to search
* @param {id} String of the language/keyboard ID
* @param a JSONArray to search
* @param id String of the language/keyboard ID
* @return int - Index if the ID is found (starting with 0). -1 if the ID doesn't exist
*/
public static int findID(JSONArray a, String id) {
@ -171,7 +171,7 @@ public class JSONUtils {
/**
* Mirror options information that comes from keyboard cloud catalog
* @param String deviceType
* @param deviceType
* @return JSONObject
*/
public static JSONObject defaultOptions(String deviceType) {

View file

@ -3,7 +3,7 @@
## 13.0 alpha
* Start version 13.0
* New Features:
* Adding a download manager to execute downloads in background (#2247,#2275)
* Adding a download manager to execute downloads in background and cleanup the existing implementation (#2247, #2275, #2308)
* Improve custom package installation: Show readme.htm before starting installation process (#2286)
* Update target Android SDK version to 29 (#2279)
* Add linting to Debug builds and resolve lint errors (#2305)