diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
index 4f5bd29dd3..a59f81dbc9 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
@@ -4,6 +4,7 @@
package com.keyman.android;
+import com.keyman.engine.util.DownloadFileUtils;
import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
import com.tavultesoft.kmapro.BuildConfig;
import com.tavultesoft.kmapro.DefaultLanguageResource;
@@ -84,7 +85,12 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false);
KMManager.setHapticFeedback(mayHaveHapticFeedback);
- KMManager.executeResourceUpdate(this);
+ // Checking for updates should never be allowed to crash the keyboard.
+ // Just silently fail if this occurs.
+ if(DownloadFileUtils.getDownloadManager(this) != null) {
+ // Will try to emit a toast if it fails - i.e., is not silent.
+ KMManager.executeResourceUpdate(this);
+ }
}
@Override
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 602112edf6..989c7716a6 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
@@ -157,6 +157,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
// Verify WebView installed and enabled before attempting to initialize KMManager
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
+
KMManager.executeResourceUpdate(this);
DefaultLanguageResource.install(context);
diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
index 01546f91ba..a0447d0f89 100644
--- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
@@ -27,7 +27,6 @@
Install Updates
-
Version: %1$s
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardDownloaderActivity.java
index e4f3055763..3f96c18289 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardDownloaderActivity.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardDownloaderActivity.java
@@ -72,7 +72,7 @@ public class KMKeyboardDownloaderActivity extends BaseActivity {
//TODO: move to keyboard manager class
private static ArrayList kbDownloadEventListeners = null;
-
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java
index 8a25ab09ba..bb8ee190b6 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java
@@ -5,11 +5,15 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
+import android.widget.Toast;
+import com.keyman.engine.R;
import com.keyman.engine.util.KMLog;
+import com.keyman.engine.util.DownloadFileUtils;
import java.io.File;
import java.util.HashMap;
@@ -206,12 +210,34 @@ public class CloudDownloadMgr{
* @param params the cloud api params for download
* @param the target models type
* @param the cloud requests result type
+ * @return `false` if the download request cannot be executed; `true` otherwise.
*/
- public void executeAsDownload(Context aContext, String aDownloadIdentifier,
+ public boolean executeAsDownload(Context aContext, String aDownloadIdentifier,
ModelType aTargetModel,
ICloudDownloadCallback aCallback,
- CloudApiTypes.CloudApiParam... params)
- {
+ CloudApiTypes.CloudApiParam... params) {
+ try {
+ executeAsDownloadInternal(aContext, aDownloadIdentifier, aTargetModel, aCallback, params);
+ } catch (DownloadManagerDisabledException e) {
+ Toast.makeText(aContext,
+ aContext.getString(R.string.update_check_download_manager_disabled),
+ Toast.LENGTH_SHORT).show();
+ return false;
+ } catch (Exception e) {
+ Toast.makeText(aContext,
+ aContext.getString(R.string.update_check_unavailable),
+ Toast.LENGTH_SHORT).show();
+ KMLog.LogException(TAG, "Unexpected exception occurred during download/query attempt", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void executeAsDownloadInternal(Context aContext, String aDownloadIdentifier,
+ ModelType aTargetModel,
+ ICloudDownloadCallback aCallback,
+ CloudApiTypes.CloudApiParam... params) throws DownloadManagerDisabledException {
if(!isInitialized) {
Log.w(TAG, "DownloadManager not initialized. Initializing CloudDownloadMgr.");
initialize(aContext);
@@ -219,16 +245,22 @@ public class CloudDownloadMgr{
KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.executeAsDownload() called; already initialized", true);
}
+ DownloadManager downloadManager = DownloadFileUtils.getDownloadManager(aContext);
+ if(downloadManager == null) {
+ // The callback object provided to us provides no way to directly signal a
+ // failure. That said, we can also immediately detect that we WILL fail and
+ // corresponding error _now_, rather than later.
+ //
+ // Unique custom error so it's easy to explicitly filter.
+ throw new DownloadManagerDisabledException();
+ }
+
synchronized (downloadSetByDownloadIdentifier) {
if (alreadyDownloadingData(aDownloadIdentifier) || params == null) {
return;
}
- DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE);
- if(downloadManager==null)
- throw new IllegalStateException("DownloadManager is not available");
-
aCallback.initializeContext(aContext);
CloudApiTypes.CloudDownloadSet _downloadSet =
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/DownloadManagerDisabledException.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/DownloadManagerDisabledException.java
new file mode 100644
index 0000000000..2011ad0eb0
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/DownloadManagerDisabledException.java
@@ -0,0 +1,7 @@
+package com.keyman.engine.cloud;
+
+public class DownloadManagerDisabledException extends RuntimeException {
+ DownloadManagerDisabledException() {
+ super("System service DownloadManager is not available and cannot facilitate downloads or queries.");
+ }
+}
\ No newline at end of file
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/impl/CloudLexicalModelMetaDataDownloadCallback.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/impl/CloudLexicalModelMetaDataDownloadCallback.java
index 2063d2d10f..62f65a96ed 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/impl/CloudLexicalModelMetaDataDownloadCallback.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/impl/CloudLexicalModelMetaDataDownloadCallback.java
@@ -111,8 +111,8 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
BaseActivity.makeToast(aContext, R.string.dictionary_download_start_in_background, Toast.LENGTH_SHORT);
- CloudDownloadMgr.getInstance().executeAsDownload(aContext,
- _r.additionalDownloadid, null, _callback,
+ CloudDownloadMgr.getInstance().executeAsDownload(
+ aContext, _r.additionalDownloadid, null, _callback,
_r.additionalDownloads.toArray(new CloudApiTypes.CloudApiParam[0]));
}
}
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/data/CloudRepository.java b/android/KMEA/app/src/main/java/com/keyman/engine/data/CloudRepository.java
index 4aad0a4e9f..4676d59c77 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/data/CloudRepository.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/data/CloudRepository.java
@@ -13,12 +13,14 @@ import com.keyman.engine.KMManager;
import com.keyman.engine.KeyboardPickerActivity;
import com.keyman.engine.R;
import com.keyman.engine.cloud.CloudApiTypes;
+import com.keyman.engine.cloud.DownloadManagerDisabledException;
import com.keyman.engine.cloud.impl.CloudCatalogDownloadCallback;
import com.keyman.engine.cloud.impl.CloudCatalogDownloadReturns;
import com.keyman.engine.cloud.CloudDataJsonUtil;
import com.keyman.engine.cloud.CloudDownloadMgr;
import com.keyman.engine.packages.JSONUtils;
import com.keyman.engine.util.BCP47;
+import com.keyman.engine.util.DownloadFileUtils;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.util.VersionUtils;
@@ -366,6 +368,10 @@ public class CloudRepository {
* @param onFailure A callback to be triggered upon failure of a query.
*/
private void downloadMetaDataFromServer(@NonNull Context context, UpdateHandler updateHandler, Runnable onSuccess, Runnable onFailure) {
+ if(DownloadFileUtils.getDownloadManager(context) == null) {
+ onFailure.run();
+ return;
+ }
boolean cacheValid = getCacheValidity(context);
// For local and PR test builds, force download of metadata
@@ -405,8 +411,13 @@ public class CloudRepository {
BaseActivity.makeToast(context, R.string.catalog_download_is_running_in_background, Toast.LENGTH_SHORT);
} else {
updateIsRunning = true;
- CloudDownloadMgr.getInstance().executeAsDownload(
+ boolean executionStarted = CloudDownloadMgr.getInstance().executeAsDownload(
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
+ if(!executionStarted) {
+ // Since we couldn't initiate the execution's async components,
+ // we need to immediately clear the update-running flag.
+ updateIsRunning = false;
+ }
}
}
}
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/logic/ResourcesUpdateTool.java b/android/KMEA/app/src/main/java/com/keyman/engine/logic/ResourcesUpdateTool.java
index 9f02480bf2..8ff46bb0b7 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/logic/ResourcesUpdateTool.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/logic/ResourcesUpdateTool.java
@@ -122,6 +122,8 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
return;
}
+ // Warning: can be run by our system keyboard, which will attempt to
+ // display the toast if it or the app is visible!
BaseActivity.makeToast(currentContext, R.string.update_check_unavailable, Toast.LENGTH_SHORT);
lastUpdateCheck = Calendar.getInstance();
updateCheckFailed = true;
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/DownloadFileUtils.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/DownloadFileUtils.java
index c16d3d381d..d9315f3f4d 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/util/DownloadFileUtils.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/DownloadFileUtils.java
@@ -3,7 +3,9 @@
*/
package com.keyman.engine.util;
+import android.app.DownloadManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
@@ -21,6 +23,37 @@ import java.io.InputStream;
public final class DownloadFileUtils {
private static final String TAG = "DownloadFileUtils";
+ private static final String DOWNLOAD_MANAGER_PACKAGE_NAME = "com.android.providers.downloads";
+
+ /**
+ * Determines whether or not Android's `DownloadManager` service is active, only
+ * returning an instance of DownloadManager when it is currently accessible and enabled.
+ * Downloads and cloud queries are impossible when it's disabled.
+ *
+ * This solution is based heavily on
+ * https://gist.github.com/Folyd/b9412bb6e2b06eb511f7.
+ * @return A valid and enabled reference to the system's DownloadManager service. May be null
+ * if it is not accessible or is disabled.
+ */
+ public static DownloadManager getDownloadManager(Context context) {
+ DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ if(downloadManager==null) {
+ return null;
+ }
+
+ int state = context.getPackageManager().getApplicationEnabledSetting(DOWNLOAD_MANAGER_PACKAGE_NAME);
+
+ if(
+ state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
+ state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER ||
+ state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ ) {
+ return null;
+ };
+
+ return downloadManager;
+ }
+
/**
* Small class for returning information about a file downloaded via DownloadManager.
*/
diff --git a/android/KMEA/app/src/main/res/values/strings.xml b/android/KMEA/app/src/main/res/values/strings.xml
index 737d728154..992b419952 100644
--- a/android/KMEA/app/src/main/res/values/strings.xml
+++ b/android/KMEA/app/src/main/res/values/strings.xml
@@ -208,6 +208,9 @@
Failed to access server!
+
+ DownloadManager disabled - cannot check for updates
+
"All resources are up to date!"