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 5b5a203049..b85280c788 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 @@ -845,7 +845,13 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi public void onLexicalModelInstalled(List> lexicalModelsInstalled) { for(int i=0; ilexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i)); - KMManager.addLexicalModel(this, lexicalModelInfo); + try { + KMManager.addLexicalModel(this, lexicalModelInfo); + } catch (RuntimeException e) { + // We error-catch this in case it's called from an AsyncTask; + // this may happen when installing a new keyboard given that we attempt + // to auto-download an appropriate lexical model to match it. + } } // It would be nice to register associated lexical model 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 77b44e73ae..edac91317c 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 @@ -245,7 +245,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity { ret = downloadNonKMPKeyboard(remoteUrl, remoteLexicalModelUrl); } catch (Exception e) { ret = -1; - Log.e(TAG, "Error: " + e); + Log.e(TAG, "Error: " + e, e); } return ret; diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 5074ee4b41..7cfd024096 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -66,6 +66,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O // Lists of installed keyboards and installed lexical models private static ArrayList> keyboardsList = null; private static ArrayList> lexicalModelsList = null; + private static Dataset storageDataset = null; private static boolean checkingUpdates = false; private static int updateCount = 0; @@ -372,6 +373,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O boolean result; keyboardsList = list; result = saveList(context, KMManager.KMFilename_KeyboardsList); + notifyKeyboardsUpdate(context); return result; } @@ -385,6 +387,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O boolean result; lexicalModelsList = list; result = saveList(context, KMManager.KMFilename_LexicalModelsList); + notifyLexicalModelsUpdate(context); return result; } @@ -448,6 +451,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } } + notifyKeyboardsUpdate(context); + return result; } @@ -481,6 +486,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } } + notifyLexicalModelsUpdate(context); + return result; } @@ -495,13 +502,14 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O result = saveList(context, KMManager.KMFilename_KeyboardsList); } + notifyKeyboardsUpdate(context); + return result; } protected static void deleteKeyboard(Context context, int position) { int curKbPos = getCurrentKeyboardIndex(); boolean result = removeKeyboard(context, position); - ; if (result) { Toast.makeText(context, "Keyboard deleted", Toast.LENGTH_SHORT).show(); @@ -511,11 +519,13 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } if (position == curKbPos && listView != null) { switchKeyboard(0); - } else { + } else if(listView != null) { // A bit of a hack, since LanguageSettingsActivity calls this method too. curKbPos = getCurrentKeyboardIndex(); setSelection(curKbPos); } } + + notifyKeyboardsUpdate(context); } /** @@ -556,6 +566,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O result = saveList(context, KMManager.KMFilename_LexicalModelsList); } + notifyLexicalModelsUpdate(context); + return result; } @@ -574,6 +586,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O Toast.makeText(context, "Model deleted", Toast.LENGTH_SHORT).show(); KMManager.deregisterLexicalModel(modelID); } + + notifyLexicalModelsUpdate(context); } @SuppressWarnings("unchecked") @@ -596,6 +610,10 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } public static Dataset getInstalledDataset(Context context) { + if(storageDataset != null) { + return storageDataset; + } + List> kbdMapList = getKeyboardsList(context); List kbdsList = new ArrayList<>(kbdMapList.size()); @@ -604,17 +622,51 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } List> lexMapList = getLexicalModelsList(context); + if(lexMapList == null) { + lexMapList = new ArrayList<>(0); + } List lexList = new ArrayList<>(lexMapList.size()); for(Map map: lexMapList) { lexList.add(new LexicalModel(map)); } - Dataset data = new Dataset(context); - data.keyboards.addAll(kbdsList); - data.lexicalModels.addAll(lexList); + storageDataset = new Dataset(context); + storageDataset.keyboards.addAll(kbdsList); + storageDataset.lexicalModels.addAll(lexList); - return data; + return storageDataset; + } + + // While the two following methods aren't exactly ideal, they should be enough to get the job done + // for 12.0 before a more complete refactor of this class is done. + protected static void notifyKeyboardsUpdate(Context context) { + Dataset storage = getInstalledDataset(context); + storage.keyboards.setNotifyOnChange(false); + storage.keyboards.clear(); + + List> mapList = getKeyboardsList(context); + List kbdList = new ArrayList<>(mapList.size()); + for(Map map: mapList) { + kbdList.add(new Keyboard(map)); + } + storage.keyboards.addAll(kbdList); + storage.keyboards.notifyDataSetChanged(); + } + + protected static void notifyLexicalModelsUpdate(Context context) { + Dataset storage = getInstalledDataset(context); + storage.lexicalModels.setNotifyOnChange(false); + storage.lexicalModels.clear(); + + List> mapList = getLexicalModelsList(context); + List lexList = new ArrayList<>(mapList.size()); + for(Map map: mapList) { + lexList.add(new LexicalModel(map)); + } + storage.lexicalModels.addAll(lexList); + + storage.lexicalModels.notifyDataSetChanged(); } protected static ArrayList> getKeyboardsList(Context context) { @@ -826,6 +878,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O } } + // TODO: Handle within the new CloudRepository class! private static void checkKeyboardUpdates(final Context context) { new AsyncTask() { private final boolean hasConnection = KMManager.hasConnection(context); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java index 86d50e140b..58ee437f04 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java @@ -6,7 +6,6 @@ package com.tavultesoft.kmea; import android.content.Context; import android.content.Intent; -import android.graphics.Typeface; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -16,8 +15,6 @@ import android.view.Window; import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -33,7 +30,6 @@ import com.tavultesoft.kmea.data.Keyboard; import com.tavultesoft.kmea.data.adapters.NestedAdapter; import com.tavultesoft.kmea.util.MapCompat; -import java.util.ArrayList; import java.util.HashMap; /** @@ -126,10 +122,6 @@ public final class LanguageSettingsActivity extends AppCompatActivity { * imageView.setImageResource(R.drawable.ic_arrow_forward); */ -// String[] from = new String[]{KMManager.KMKey_KeyboardName, KMManager.KMKey_Icon}; -// int[] to = new int[]{R.id.text1, R.id.image1}; -// ListAdapter listAdapter = new KMListAdapter(context, associatedKeyboardList, R.layout.list_row_layout1, from, to); - listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -190,6 +182,14 @@ public final class LanguageSettingsActivity extends AppCompatActivity { @Override public void onResume() { super.onResume(); + + FilteredKeyboardsAdapter adapter = ((FilteredKeyboardsAdapter) listView.getAdapter()); + + if(adapter != null) { + // Despite the fact that updates should have been auto-triggered anyway, it seems that we + // need a manual call here for things to happen in a timely fashion. + adapter.notifyDataSetChanged(); + } } @Override @@ -211,13 +211,10 @@ public final class LanguageSettingsActivity extends AppCompatActivity { // Fully details the building of this Activity's list view items. static private class FilteredKeyboardsAdapter extends NestedAdapter { static final int RESOURCE = R.layout.list_row_layout1; - private final Context context; public FilteredKeyboardsAdapter(@NonNull Context context, final Dataset storage, final String languageCode) { // Goal: to not need a custom filter here, instead relying on LanguageDataset's built-in filters. super(context, RESOURCE, storage.keyboards, storage.keyboardFilter, languageCode); - - this.context = context; } @Override diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudRepository.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudRepository.java index 21b0a5f3fa..fea0718535 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudRepository.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/CloudRepository.java @@ -160,6 +160,16 @@ public class CloudRepository { JSONObject kbdData = new JSONObject(); JSONArray lexData = new JSONArray(); + if(loadKeyboardsFromCache) { + kbdData = getCachedJSONObject(getKeyboardCacheFile(context)); + + // In case something went wrong with the last cache attempt, which can cause a null return. + if(kbdData == null) { + kbdData = new JSONObject(); + loadKeyboardsFromCache = false; + } + } + if(!loadKeyboardsFromCache) { String deviceType = context.getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { @@ -174,8 +184,15 @@ public class CloudRepository { //cloudQueries[cloudQueryEntries++] = new CloudApiParam(ApiTarget.Keyboards, keyboardURL, JSONType.Object); cloudQueries.add(new CloudApiParam(ApiTarget.Keyboards, keyboardURL, JSONType.Object)); - } else { - kbdData = getCachedJSONObject(getKeyboardCacheFile(context)); + } + + if(loadLexicalModelsFromCache) { + lexData = getCachedJSONArray(getLexicalModelCacheFile(context)); + + if(lexData == null) { + lexData = new JSONArray(); + loadLexicalModelsFromCache = false; + } } if(!loadLexicalModelsFromCache) { @@ -184,7 +201,6 @@ public class CloudRepository { // query is ready! String lexicalURL = String.format("%s?q", KMKeyboardDownloaderActivity.kKeymanApiModelURL); - //cloudQueries[cloudQueryEntries++] = new CloudApiParam(ApiTarget.LexicalModels, lexicalURL, JSONType.Array); cloudQueries.add(new CloudApiParam(ApiTarget.LexicalModels, lexicalURL, JSONType.Array)); @@ -198,8 +214,6 @@ public class CloudRepository { // lexicalURL = lexicalURL.substring(0, lexicalURL.lastIndexOf(',')); /* do what's possible here, rather than in the Task */ - } else { - lexData = getCachedJSONArray(getLexicalModelCacheFile(context)); } boolean executeCallbacks = true; @@ -214,7 +228,7 @@ public class CloudRepository { } // Reuse any valid parts of the cache. - if(kbdData != null || lexData != null) { + if(loadKeyboardsFromCache || loadLexicalModelsFromCache) { CloudDownloadReturns jsonData = new CloudDownloadReturns(kbdData, lexData); // Call the processor method directly with the cached API data. @@ -651,6 +665,7 @@ public class CloudRepository { } public void processCloudReturns(CloudDownloadReturns jsonTuple, boolean executeCallbacks) { + // TODO: Need proper offline check again. List keyboardsArrayList = processKeyboardJSON(jsonTuple.keyboardJSON, false); List lexicalModelsArrayList = processLexicalModelJSON(jsonTuple.lexicalModelJSON); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/Dataset.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/Dataset.java index 66a2a432f9..8f13b5d016 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/Dataset.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/Dataset.java @@ -99,7 +99,17 @@ public class Dataset extends ArrayAdapter { super.add(object); - getMetadataFor(object); // We can ignore the return; this makes sure the needed object is constructed. + LanguageDataset data = getMetadataFor(object); // We can ignore the return; this makes sure the needed object is constructed. + + if(object instanceof Keyboard) { + if(!data.keyboards.contains(object)) { + data.keyboards.add((Keyboard) object); + } + } else if(object instanceof LexicalModel) { + if(!data.lexicalModels.contains(object)) { + data.lexicalModels.add((LexicalModel) object); + } + } if(notify) { Dataset.this.notifyDataSetChanged(); @@ -154,6 +164,14 @@ public class Dataset extends ArrayAdapter { super.remove(object); + LanguageDataset data = getMetadataFor(object); + + if(object instanceof Keyboard) { + data.keyboards.remove(object); + } else if(object instanceof LexicalModel) { + data.lexicalModels.remove(object); + } + handleLanguageItemRemoval(object); if(notify) { @@ -168,12 +186,21 @@ public class Dataset extends ArrayAdapter { Dataset.this.setNotifyOnChange(false); } - List clearedItems = this.asList(); + // this.asList returns an unmodifiable reference to the internal list - + // but that list itself may be modified by this adapter's functions! + List clearedItems = new ArrayList<>(this.asList()); super.clear(); - for(Type kbd: clearedItems) { - handleLanguageItemRemoval(kbd); + for(Type object: clearedItems) { + LanguageDataset data = getMetadataFor(object); + if(object instanceof Keyboard) { + data.keyboards.remove(object); + } else if(object instanceof LexicalModel) { + data.lexicalModels.remove(object); + } + + handleLanguageItemRemoval(object); } if(notify) { @@ -195,11 +222,11 @@ public class Dataset extends ArrayAdapter { @Override public void notifyDataSetChanged() { - doNotify = true; - super.notifyDataSetChanged(); + Dataset.this.notifyDataSetChanged(); } void _notifyDataSetChanged() { + doNotify = true; super.notifyDataSetChanged(); } @@ -292,8 +319,8 @@ public class Dataset extends ArrayAdapter { @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); - keyboards.notifyDataSetChanged(); - lexicalModels.notifyDataSetChanged(); + keyboards._notifyDataSetChanged(); + lexicalModels._notifyDataSetChanged(); } void _notifyDataSetChanged() { diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/adapters/NestedAdapter.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/adapters/NestedAdapter.java index 72ae705d17..0b7991b314 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/adapters/NestedAdapter.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/adapters/NestedAdapter.java @@ -81,7 +81,9 @@ public class NestedAdapter & ListBacked this(context, resource, adapter, new AdapterFilter() { @Override public List selectFrom(A adapter, FilterArg dummy) { - return adapter.asList(); + // Make sure to duplicate the list so that we don't accidentally try to modify our + // wrapped adapter's (unmodifiable) contents. + return new ArrayList<>(adapter.asList()); } }, null); }