From 9820a30688f0a774eced1676f1927cacde53301f Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Mon, 14 Dec 2020 18:35:53 -0600 Subject: [PATCH] feat(Android): Refactoring configuration (#3778) Co-authored-by: Daniel Imhoff --- android/capacitor/build.gradle | 2 +- .../main/java/com/getcapacitor/Bridge.java | 34 +- .../java/com/getcapacitor/BridgeActivity.java | 13 +- .../java/com/getcapacitor/BridgeFragment.java | 6 + .../main/java/com/getcapacitor/CapConfig.java | 453 ++++++++++++++---- .../com/getcapacitor/CapacitorWebView.java | 4 +- .../main/java/com/getcapacitor/FileUtils.java | 23 + .../main/java/com/getcapacitor/JSExport.java | 26 +- .../main/java/com/getcapacitor/Logger.java | 2 +- .../main/java/com/getcapacitor/Plugin.java | 27 +- .../java/com/getcapacitor/PluginConfig.java | 116 +++++ .../java/com/getcapacitor/util/JSONUtils.java | 166 +++++++ .../com/getcapacitor/ConfigBuildingTest.java | 104 ++++ .../com/getcapacitor/ConfigReadingTest.java | 116 +++++ .../src/test/resources/configs/bad.json | 28 ++ .../src/test/resources/configs/flat.json | 17 + .../src/test/resources/configs/hierarchy.json | 26 + .../src/test/resources/configs/nonjson.json | 1 + .../src/test/resources/configs/server.json | 23 + 19 files changed, 1048 insertions(+), 139 deletions(-) create mode 100644 android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java create mode 100644 android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java create mode 100644 android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java create mode 100644 android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java create mode 100644 android/capacitor/src/test/resources/configs/bad.json create mode 100644 android/capacitor/src/test/resources/configs/flat.json create mode 100644 android/capacitor/src/test/resources/configs/hierarchy.json create mode 100644 android/capacitor/src/test/resources/configs/nonjson.json create mode 100644 android/capacitor/src/test/resources/configs/server.json diff --git a/android/capacitor/build.gradle b/android/capacitor/build.gradle index a44692257..972d2d5f7 100644 --- a/android/capacitor/build.gradle +++ b/android/capacitor/build.gradle @@ -65,6 +65,6 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" implementation "org.apache.cordova:framework:$cordovaAndroidVersion" testImplementation 'org.json:json:20140107' - testImplementation 'org.mockito:mockito-inline:2.25.1' + testImplementation 'org.mockito:mockito-inline:3.6.28' } diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index c43ce3fbe..c8a3b0efa 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -42,7 +42,6 @@ import org.apache.cordova.PluginEntry; import org.apache.cordova.PluginManager; import org.json.JSONException; -import org.json.JSONObject; /** * The Bridge class is the main engine of Capacitor. It manages @@ -137,7 +136,7 @@ public Bridge( MockCordovaInterfaceImpl cordovaInterface, PluginManager pluginManager, CordovaPreferences preferences, - JSONObject config + CapConfig config ) { this.app = new App(); this.context = context; @@ -151,7 +150,7 @@ public Bridge( handlerThread.start(); taskHandler = new Handler(handlerThread.getLooper()); - this.config = new CapConfig(getActivity().getAssets(), config); + this.config = config != null ? config : CapConfig.fromFile(getActivity()); Logger.init(this.config); // Initialize web view and message handler for it @@ -174,7 +173,7 @@ public App getApp() { private void loadWebView() { appUrlConfig = this.getServerUrl(); - String[] appAllowNavigationConfig = this.config.getArray("server.allowNavigation"); + String[] appAllowNavigationConfig = this.config.getAllowNavigation(); ArrayList authorities = new ArrayList<>(); if (appAllowNavigationConfig != null) { @@ -204,7 +203,7 @@ private void loadWebView() { } } - final boolean html5mode = this.config.getBoolean("server.html5mode", true); + final boolean html5mode = this.config.isHTML5Mode(); // Start the local web server localServer = new WebViewLocalServer(context, this, getJSInjector(), authorities, html5mode); @@ -345,7 +344,7 @@ public Uri getIntentUri() { * @return */ public String getScheme() { - return this.config.getString("server.androidScheme", CAPACITOR_HTTP_SCHEME); + return this.config.getAndroidScheme(); } /** @@ -353,7 +352,7 @@ public String getScheme() { * @return */ public String getHost() { - return this.config.getString("server.hostname", "localhost"); + return this.config.getHostname(); } /** @@ -361,7 +360,7 @@ public String getHost() { * @return */ public String getServerUrl() { - return this.config.getString("server.url"); + return this.config.getServerUrl(); } public CapConfig getConfig() { @@ -384,21 +383,21 @@ private void initWebView() { settings.setAppCacheEnabled(true); settings.setMediaPlaybackRequiresUserGesture(false); settings.setJavaScriptCanOpenWindowsAutomatically(true); - if (this.config.getBoolean("android.allowMixedContent", false)) { + if (this.config.isMixedContentAllowed()) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } - String appendUserAgent = this.config.getString("android.appendUserAgent", this.config.getString("appendUserAgent", null)); + String appendUserAgent = this.config.getAppendedUserAgentString(); if (appendUserAgent != null) { String defaultUserAgent = settings.getUserAgentString(); settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent); } - String overrideUserAgent = this.config.getString("android.overrideUserAgent", this.config.getString("overrideUserAgent", null)); + String overrideUserAgent = this.config.getOverriddenUserAgentString(); if (overrideUserAgent != null) { settings.setUserAgentString(overrideUserAgent); } - String backgroundColor = this.config.getString("android.backgroundColor", this.config.getString("backgroundColor", null)); + String backgroundColor = this.config.getBackgroundColor(); try { if (backgroundColor != null) { webView.setBackgroundColor(WebColor.parseColor(backgroundColor)); @@ -406,12 +405,9 @@ private void initWebView() { } catch (IllegalArgumentException ex) { Logger.debug("WebView background color not applied"); } - boolean defaultDebuggable = false; - if (isDevMode()) { - defaultDebuggable = true; - } + webView.requestFocusFromTouch(); - WebView.setWebContentsDebuggingEnabled(this.config.getBoolean("android.webContentsDebuggingEnabled", defaultDebuggable)); + WebView.setWebContentsDebuggingEnabled(this.config.isWebContentsDebuggingEnabled()); } /** @@ -1119,7 +1115,7 @@ public void setWebViewClient(BridgeWebViewClient client) { public static class Builder { private Bundle instanceState = null; - private JSONObject config = new JSONObject(); + private CapConfig config = null; private List> plugins = new ArrayList<>(); private AppCompatActivity activity = null; private Context context = null; @@ -1137,7 +1133,7 @@ public Builder setInstanceState(Bundle instanceState) { return this; } - public Builder setConfig(JSONObject config) { + public Builder setConfig(CapConfig config) { this.config = config; return this; } diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java index d103e3157..aeed9e10e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java @@ -7,13 +7,13 @@ import com.getcapacitor.android.R; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; public class BridgeActivity extends AppCompatActivity { protected Bridge bridge; protected boolean keepRunning = true; - private JSONObject config; + private CapConfig config; + private int activityDepth = 0; private List> initialPlugins = new ArrayList<>(); private final Bridge.Builder bridgeBuilder = new Bridge.Builder(); @@ -25,9 +25,12 @@ protected void onCreate(Bundle savedInstanceState) { } /** + * Initializes the Capacitor Bridge with the Activity. * @deprecated It is preferred not to call this method. If it is not called, the bridge is * initialized automatically. If you need to add additional plugins during initialization, * use {@link #registerPlugin(Class)} or {@link #registerPlugins(List)}. + * + * @param plugins A list of plugins to initialize with Capacitor */ @Deprecated protected void init(Bundle savedInstanceState, List> plugins) { @@ -35,12 +38,16 @@ protected void init(Bundle savedInstanceState, List> plu } /** + * Initializes the Capacitor Bridge with the Activity. * @deprecated It is preferred not to call this method. If it is not called, the bridge is * initialized automatically. If you need to add additional plugins during initialization, * use {@link #registerPlugin(Class)} or {@link #registerPlugins(List)}. + * + * @param plugins A list of plugins to initialize with Capacitor + * @param config An instance of a Capacitor Configuration to use. If null, will load from file */ @Deprecated - protected void init(Bundle savedInstanceState, List> plugins, JSONObject config) { + protected void init(Bundle savedInstanceState, List> plugins, CapConfig config) { this.initialPlugins = plugins; this.config = config; diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java index 093f44ab1..b0b1f6289 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java @@ -26,6 +26,7 @@ public class BridgeFragment extends Fragment { protected boolean keepRunning = true; private final List> initialPlugins = new ArrayList<>(); + private CapConfig config = null; public BridgeFragment() { // Required empty public constructor @@ -50,6 +51,10 @@ public void addPlugin(Class plugin) { this.initialPlugins.add(plugin); } + public void setConfig(CapConfig config) { + this.config = config; + } + /** * Load the WebView and create the Bridge */ @@ -68,6 +73,7 @@ protected void load(Bundle savedInstanceState) { .setActivity((AppCompatActivity) getActivity()) .setInstanceState(savedInstanceState) .setPlugins(initialPlugins) + .setConfig(config) .create(); if (startDir != null) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java b/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java index 3f7eb90e3..d574b8f20 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java +++ b/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java @@ -1,41 +1,102 @@ package com.getcapacitor; -import android.content.res.AssetManager; -import java.io.BufferedReader; +import static com.getcapacitor.Bridge.CAPACITOR_HTTP_SCHEME; +import static com.getcapacitor.FileUtils.readFile; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import com.getcapacitor.util.JSONUtils; import java.io.IOException; -import java.io.InputStreamReader; -import org.json.JSONArray; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import org.json.JSONException; import org.json.JSONObject; /** - * Management interface for accessing values in capacitor.config.json + * Represents the configuration options for Capacitor */ public class CapConfig { - private JSONObject config = new JSONObject(); + // Server Config + private boolean html5mode = true; + private String serverUrl; + private String hostname = "localhost"; + private String androidScheme = CAPACITOR_HTTP_SCHEME; + private String[] allowNavigation; + + // Android Config + private String overriddenUserAgentString; + private String appendedUserAgentString; + private String backgroundColor; + private boolean allowMixedContent = false; + private boolean captureInput = false; + private boolean webContentsDebuggingEnabled = false; + private boolean hideLogs = false; + + // Plugins + private Map pluginsConfiguration = null; + + // Config Object JSON (legacy) + private JSONObject configJSON = new JSONObject(); - public CapConfig(AssetManager assetManager, JSONObject config) { - if (config != null) { - this.config = config; - } else { - // Load our capacitor.config.json - this.loadConfig(assetManager); + /** + * Constructs an empty config file. + */ + private CapConfig() {} + + /** + * Constructs a Capacitor Configuration from config.json file. + * + * @param context The context. + * @return A loaded config file, if successful. + */ + static CapConfig fromFile(Context context) { + CapConfig config = new CapConfig(); + + if (context == null) { + Logger.error("Capacitor Config could not be created from file. Context must not be null."); + return config; } + + config.loadConfig(context); + config.deserializeConfig(context); + return config; } - private void loadConfig(AssetManager assetManager) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open("capacitor.config.json")))) { - // do reading, usually loop until end of file reading - StringBuilder b = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - //process line - b.append(line); - } + /** + * Constructs a Capacitor Configuration using ConfigBuilder. + * + * @param builder A config builder initialized with values + */ + private CapConfig(Builder builder) { + // Server Config + this.html5mode = builder.html5mode; + this.serverUrl = builder.serverUrl; + this.hostname = builder.hostname; + this.androidScheme = builder.androidScheme; + this.allowNavigation = builder.allowNavigation; + + // Android Config + this.overriddenUserAgentString = builder.overriddenUserAgentString; + this.appendedUserAgentString = builder.appendedUserAgentString; + this.backgroundColor = builder.backgroundColor; + this.allowMixedContent = builder.allowMixedContent; + this.captureInput = builder.captureInput; + this.webContentsDebuggingEnabled = builder.webContentsDebuggingEnabled; + this.hideLogs = builder.hideLogs; + + // Plugins Config + this.pluginsConfiguration = builder.pluginsConfiguration; + } - String jsonString = b.toString(); - this.config = new JSONObject(jsonString); + /** + * Loads a Capacitor Configuration JSON file into a Capacitor Configuration object. + */ + private void loadConfig(Context context) { + try { + String jsonString = readFile(context, "capacitor.config.json"); + configJSON = new JSONObject(jsonString); } catch (IOException ex) { Logger.error("Unable to load capacitor.config.json. Run npx cap copy first", ex); } catch (JSONException ex) { @@ -43,96 +104,316 @@ private void loadConfig(AssetManager assetManager) { } } - public JSONObject getObject(String key) { - try { - return this.config.getJSONObject(key); - } catch (Exception ex) {} - return null; + /** + * Deserializes the config from JSON into a Capacitor Configuration object. + */ + private void deserializeConfig(Context context) { + // Server + html5mode = JSONUtils.getBoolean(configJSON, "server.html5mode", html5mode); + serverUrl = JSONUtils.getString(configJSON, "server.url", null); + hostname = JSONUtils.getString(configJSON, "server.hostname", hostname); + androidScheme = JSONUtils.getString(configJSON, "server.androidScheme", androidScheme); + allowNavigation = JSONUtils.getArray(configJSON, "server.allowNavigation", null); + + // Android + overriddenUserAgentString = + JSONUtils.getString(configJSON, "android.overrideUserAgent", JSONUtils.getString(configJSON, "overrideUserAgent", null)); + appendedUserAgentString = + JSONUtils.getString(configJSON, "android.appendUserAgent", JSONUtils.getString(configJSON, "appendUserAgent", null)); + backgroundColor = + JSONUtils.getString(configJSON, "android.backgroundColor", JSONUtils.getString(configJSON, "backgroundColor", null)); + allowMixedContent = + JSONUtils.getBoolean( + configJSON, + "android.allowMixedContent", + JSONUtils.getBoolean(configJSON, "allowMixedContent", allowMixedContent) + ); + captureInput = JSONUtils.getBoolean(configJSON, "android.captureInput", captureInput); + hideLogs = JSONUtils.getBoolean(configJSON, "android.hideLogs", JSONUtils.getBoolean(configJSON, "hideLogs", hideLogs)); + webContentsDebuggingEnabled = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + webContentsDebuggingEnabled = JSONUtils.getBoolean(configJSON, "android.webContentsDebuggingEnabled", webContentsDebuggingEnabled); + + // Plugins + pluginsConfiguration = deserializePluginsConfig(JSONUtils.getObject(configJSON, "plugins")); } - private JSONObject getConfigObjectDeepest(String key) throws JSONException { - // Split on periods - String[] parts = key.split("\\."); + public boolean isHTML5Mode() { + return html5mode; + } - JSONObject o = this.config; - // Search until the second to last part of the key - for (int i = 0; i < parts.length - 1; i++) { - String k = parts[i]; - o = o.getJSONObject(k); - } - return o; + public String getServerUrl() { + return serverUrl; } - public String getString(String key) { - return getString(key, null); + public String getHostname() { + return hostname; } - public String getString(String key, String defaultValue) { - String k = getConfigKey(key); - try { - JSONObject o = this.getConfigObjectDeepest(key); + public String getAndroidScheme() { + return androidScheme; + } - String value = o.getString(k); - if (value == null) { - return defaultValue; - } - return value; - } catch (Exception ex) {} - return defaultValue; + public String[] getAllowNavigation() { + return allowNavigation; } - public boolean getBoolean(String key, boolean defaultValue) { - String k = getConfigKey(key); - try { - JSONObject o = this.getConfigObjectDeepest(key); + public String getOverriddenUserAgentString() { + return overriddenUserAgentString; + } - return o.getBoolean(k); - } catch (Exception ex) {} - return defaultValue; + public String getAppendedUserAgentString() { + return appendedUserAgentString; } - public int getInt(String key, int defaultValue) { - String k = getConfigKey(key); - try { - JSONObject o = this.getConfigObjectDeepest(key); - return o.getInt(k); - } catch (Exception ignore) { - // value was not found - } - return defaultValue; + public String getBackgroundColor() { + return backgroundColor; + } + + public boolean isMixedContentAllowed() { + return allowMixedContent; } - private String getConfigKey(String key) { - String[] parts = key.split("\\."); - if (parts.length > 0) { - return parts[parts.length - 1]; + public boolean isInputCaptured() { + return captureInput; + } + + public boolean isWebContentsDebuggingEnabled() { + return webContentsDebuggingEnabled; + } + + public boolean isLogsHidden() { + return hideLogs; + } + + public PluginConfig getPluginConfiguration(String pluginId) { + PluginConfig pluginConfig = pluginsConfiguration.get(pluginId); + if (pluginConfig == null) { + pluginConfig = new PluginConfig(new JSONObject()); } + + return pluginConfig; + } + + /** + * Get a JSON object value from the Capacitor config. + * @deprecated use {@link PluginConfig#getObject(String)} to access plugin config values. + * For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @return The value from the config, if exists. Null if not + */ + @Deprecated + public JSONObject getObject(String key) { + try { + return configJSON.getJSONObject(key); + } catch (Exception ex) {} return null; } + /** + * Get a string value from the Capacitor config. + * @deprecated use {@link PluginConfig#getString(String, String)} to access plugin config + * values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @return The value from the config, if exists. Null if not + */ + @Deprecated + public String getString(String key) { + return JSONUtils.getString(configJSON, key, null); + } + + /** + * Get a string value from the Capacitor config. + * @deprecated use {@link PluginConfig#getString(String, String)} to access plugin config + * values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + @Deprecated + public String getString(String key, String defaultValue) { + return JSONUtils.getString(configJSON, key, defaultValue); + } + + /** + * Get a boolean value from the Capacitor config. + * @deprecated use {@link PluginConfig#getBoolean(String, boolean)} to access plugin config + * values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + @Deprecated + public boolean getBoolean(String key, boolean defaultValue) { + return JSONUtils.getBoolean(configJSON, key, defaultValue); + } + + /** + * Get an integer value from the Capacitor config. + * @deprecated use {@link PluginConfig#getInt(String, int)} to access the plugin config + * values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + @Deprecated + public int getInt(String key, int defaultValue) { + return JSONUtils.getInt(configJSON, key, defaultValue); + } + + /** + * Get a string array value from the Capacitor config. + * @deprecated use {@link PluginConfig#getArray(String)} to access the plugin config + * values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @return The value from the config, if exists. Null if not + */ + @Deprecated public String[] getArray(String key) { - return getArray(key, null); + return JSONUtils.getArray(configJSON, key, null); } + /** + * Get a string array value from the Capacitor config. + * @deprecated use {@link PluginConfig#getArray(String, String[])} to access the plugin + * config values. For main Capacitor config values, use the appropriate getter. + * + * @param key A key to fetch from the config + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + @Deprecated public String[] getArray(String key, String[] defaultValue) { - String k = getConfigKey(key); - try { - JSONObject o = this.getConfigObjectDeepest(key); + return JSONUtils.getArray(configJSON, key, defaultValue); + } - JSONArray a = o.getJSONArray(k); - if (a == null) { - return defaultValue; - } + private static Map deserializePluginsConfig(JSONObject pluginsConfig) { + Map pluginsMap = new HashMap<>(); - int l = a.length(); - String[] value = new String[l]; + // return an empty map if there is no pluginsConfig json + if (pluginsConfig == null) { + return pluginsMap; + } - for (int i = 0; i < l; i++) { - value[i] = (String) a.get(i); + Iterator pluginIds = pluginsConfig.keys(); + + while (pluginIds.hasNext()) { + String pluginId = pluginIds.next(); + JSONObject value = null; + + try { + value = pluginsConfig.getJSONObject(pluginId); + PluginConfig pluginConfig = new PluginConfig(value); + pluginsMap.put(pluginId, pluginConfig); + } catch (JSONException e) { + e.printStackTrace(); } + } - return value; - } catch (Exception ex) {} - return defaultValue; + return pluginsMap; + } + + /** + * Builds a Capacitor Configuration in code + */ + public static class Builder { + + // Server Config Values + private boolean html5mode = true; + private String serverUrl; + private String hostname = "localhost"; + private String androidScheme = CAPACITOR_HTTP_SCHEME; + private String[] allowNavigation; + + // Android Config Values + private String overriddenUserAgentString; + private String appendedUserAgentString; + private String backgroundColor; + private boolean allowMixedContent = false; + private boolean captureInput = false; + private boolean webContentsDebuggingEnabled = false; + private boolean hideLogs = false; + + // Plugins Config Object + private Map pluginsConfiguration = new HashMap<>(); + + /** + * Builds a Capacitor Config from the builder. + * + * @return A new Capacitor Config + */ + public CapConfig create() { + return new CapConfig(this); + } + + public Builder setPluginsConfiguration(JSONObject pluginsConfiguration) { + this.pluginsConfiguration = deserializePluginsConfig(pluginsConfiguration); + return this; + } + + public Builder setHTML5mode(boolean html5mode) { + this.html5mode = html5mode; + return this; + } + + public Builder setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + return this; + } + + public Builder setHostname(String hostname) { + this.hostname = hostname; + return this; + } + + public Builder setAndroidScheme(String androidScheme) { + this.androidScheme = androidScheme; + return this; + } + + public Builder setAllowNavigation(String[] allowNavigation) { + this.allowNavigation = allowNavigation; + return this; + } + + public Builder setOverriddenUserAgentString(String overriddenUserAgentString) { + this.overriddenUserAgentString = overriddenUserAgentString; + return this; + } + + public Builder setAppendedUserAgentString(String appendedUserAgentString) { + this.appendedUserAgentString = appendedUserAgentString; + return this; + } + + public Builder setBackgroundColor(String backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public Builder setAllowMixedContent(boolean allowMixedContent) { + this.allowMixedContent = allowMixedContent; + return this; + } + + public Builder setCaptureInput(boolean captureInput) { + this.captureInput = captureInput; + return this; + } + + public Builder setWebContentsDebuggingEnabled(boolean webContentsDebuggingEnabled) { + this.webContentsDebuggingEnabled = webContentsDebuggingEnabled; + return this; + } + + public Builder setLogsHidden(boolean hideLogs) { + this.hideLogs = hideLogs; + return this; + } } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java b/android/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java index d5185ac65..73d13d5b2 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java +++ b/android/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java @@ -18,8 +18,8 @@ public CapacitorWebView(Context context, AttributeSet attrs) { @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - CapConfig config = new CapConfig(getContext().getAssets(), null); - boolean captureInput = config.getBoolean("android.captureInput", false); + CapConfig config = CapConfig.fromFile(getContext()); + boolean captureInput = config.isInputCaptured(); if (captureInput) { if (capInputConnection == null) { capInputConnection = new BaseInputConnection(this, false); diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java index 0451dfd98..9f1943064 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java @@ -33,9 +33,12 @@ import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; /** * Common File utilities, such as resolve content URIs and @@ -132,6 +135,26 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { return null; } + /** + * Read a plaintext file. + * + * @param context Used to get access to the asset manager to open the file. + * @param fileName The path of the file to read. + * @return The contents of the file path. + * @throws IOException Thrown if any issues reading the provided file path. + */ + static String readFile(Context context, String fileName) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(context.getAssets().open(fileName)))) { + StringBuffer buffer = new StringBuffer(); + String line; + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + + return buffer.toString(); + } + } + /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. diff --git a/android/capacitor/src/main/java/com/getcapacitor/JSExport.java b/android/capacitor/src/main/java/com/getcapacitor/JSExport.java index f3f33386c..1841a1c67 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/JSExport.java +++ b/android/capacitor/src/main/java/com/getcapacitor/JSExport.java @@ -1,10 +1,10 @@ package com.getcapacitor; +import static com.getcapacitor.FileUtils.readFile; + import android.content.Context; import android.text.TextUtils; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -18,26 +18,10 @@ public static String getGlobalJS(Context context, boolean isDebug) { return "window.Capacitor = { DEBUG: " + isDebug + ", Plugins: {} };"; } - private static String getJS(Context context, String fileName) throws IOException { - try { - BufferedReader br = new BufferedReader(new InputStreamReader(context.getAssets().open(fileName))); - - StringBuffer b = new StringBuffer(); - String line; - while ((line = br.readLine()) != null) { - b.append(line + "\n"); - } - - return b.toString(); - } catch (IOException ex) { - throw ex; - } - } - public static String getCordovaJS(Context context) { String fileContent = ""; try { - fileContent = getJS(context, "public/cordova.js"); + fileContent = readFile(context, "public/cordova.js"); } catch (IOException ex) { Logger.error("Unable to read public/cordova.js file, Cordova plugins will not work"); } @@ -47,7 +31,7 @@ public static String getCordovaJS(Context context) { public static String getCordovaPluginsFileJS(Context context) { String fileContent = ""; try { - fileContent = getJS(context, "public/cordova_plugins.js"); + fileContent = readFile(context, "public/cordova_plugins.js"); } catch (IOException ex) { Logger.error("Unable to read public/cordova_plugins.js file, Cordova plugins will not work"); } @@ -103,7 +87,7 @@ public static String getFilesContent(Context context, String path) { builder.append(getFilesContent(context, path + "/" + file)); } } else { - return getJS(context, path); + return readFile(context, path); } } catch (IOException ex) { Logger.error("Unable to read file at path " + path); diff --git a/android/capacitor/src/main/java/com/getcapacitor/Logger.java b/android/capacitor/src/main/java/com/getcapacitor/Logger.java index 513ea5f87..b90f7bbba 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Logger.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Logger.java @@ -98,6 +98,6 @@ public static void error(String tag, String message, Throwable e) { } protected static boolean shouldLog() { - return config == null || !config.getBoolean("android.hideLogs", config.getBoolean("hideLogs", false)); + return config == null || !config.isLogsHidden(); } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index b56998662..bb12a9ebe 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -168,14 +168,29 @@ public PluginCall getSavedCall() { return this.savedLastCall; } + /** + * Get the config options for this plugin. + * + * @return a config object representing the plugin config options, or an empty config + * if none exists + */ + public PluginConfig getConfig() { + return bridge.getConfig().getPluginConfiguration(handle.getId()); + } + + /** + * Get the value for a key on the config for this plugin. + * @deprecated use {@link #getConfig()} and access config values using the methods available + * depending on the type. + * + * @param key the key for the config value + * @return some object containing the value from the config + */ + @Deprecated public Object getConfigValue(String key) { try { - JSONObject plugins = bridge.getConfig().getObject("plugins"); - if (plugins == null) { - return null; - } - JSONObject pluginConfig = plugins.getJSONObject(getPluginHandle().getId()); - return pluginConfig.get(key); + PluginConfig pluginConfig = getConfig(); + return pluginConfig.getConfigJSON().get(key); } catch (JSONException ex) { return null; } diff --git a/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java new file mode 100644 index 000000000..0f00fc530 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java @@ -0,0 +1,116 @@ +package com.getcapacitor; + +import com.getcapacitor.util.JSONUtils; +import org.json.JSONObject; + +/** + * Represents the configuration options for plugins used by Capacitor + */ +public class PluginConfig { + + /** + * The object containing plugin config values. + */ + private final JSONObject config; + + /** + * Constructs a PluginsConfig with the provided JSONObject value. + * + * @param config A plugin configuration expressed as a JSON Object + */ + PluginConfig(JSONObject config) { + this.config = config; + } + + /** + * Get a string value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @return The value from the config, if exists. Null if not + */ + public String getString(String configKey) { + return getString(configKey, null); + } + + /** + * Get a string value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + public String getString(String configKey, String defaultValue) { + return JSONUtils.getString(config, configKey, defaultValue); + } + + /** + * Get a boolean value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + public boolean getBoolean(String configKey, boolean defaultValue) { + return JSONUtils.getBoolean(config, configKey, defaultValue); + } + + /** + * Get an integer value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + public int getInt(String configKey, int defaultValue) { + return JSONUtils.getInt(config, configKey, defaultValue); + } + + /** + * Get a string array value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @return The value from the config, if exists. Null if not + */ + public String[] getArray(String configKey) { + return getArray(configKey, null); + } + + /** + * Get a string array value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + public String[] getArray(String configKey, String[] defaultValue) { + return JSONUtils.getArray(config, configKey, defaultValue); + } + + /** + * Get a JSON object value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @return The value from the config, if exists. Null if not + */ + public JSONObject getObject(String configKey) { + return JSONUtils.getObject(config, configKey); + } + + /** + * Check if the PluginConfig is empty. + * + * @return true if the plugin config has no entries + */ + public boolean isEmpty() { + return config.length() == 0; + } + + /** + * Gets the JSON Object containing the config of the the provided plugin ID. + * + * @return The config for that plugin + */ + public JSONObject getConfigJSON() { + return config; + } +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java new file mode 100644 index 000000000..1d2fc2078 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java @@ -0,0 +1,166 @@ +package com.getcapacitor.util; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Helper methods for parsing JSON objects. + */ +public class JSONUtils { + + /** + * Get a string value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @param defaultValue A default value to return if the key cannot be found + * @return The value at the given key in the JSON object, or the default value + */ + public static String getString(JSONObject jsonObject, String key, String defaultValue) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + + String value = o.getString(k); + if (value == null) { + return defaultValue; + } + return value; + } catch (JSONException ignore) { + // value was not found + } + + return defaultValue; + } + + /** + * Get a boolean value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @param defaultValue A default value to return if the key cannot be found + * @return The value at the given key in the JSON object, or the default value + */ + public static boolean getBoolean(JSONObject jsonObject, String key, boolean defaultValue) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + + return o.getBoolean(k); + } catch (JSONException ignore) { + // value was not found + } + + return defaultValue; + } + + /** + * Get an int value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @param defaultValue A default value to return if the key cannot be found + * @return The value at the given key in the JSON object, or the default value + */ + public static int getInt(JSONObject jsonObject, String key, int defaultValue) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + return o.getInt(k); + } catch (JSONException ignore) { + // value was not found + } + + return defaultValue; + } + + /** + * Get a JSON object value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @return The value from the config, if exists. Null if not + */ + public static JSONObject getObject(JSONObject jsonObject, String key) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + + return o.getJSONObject(k); + } catch (JSONException ignore) { + // value was not found + } + + return null; + } + + /** + * Get a string array value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @param defaultValue A default value to return if the key cannot be found + * @return The value at the given key in the JSON object, or the default value + */ + public static String[] getArray(JSONObject jsonObject, String key, String[] defaultValue) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + + JSONArray a = o.getJSONArray(k); + if (a == null) { + return defaultValue; + } + + int l = a.length(); + String[] value = new String[l]; + + for (int i = 0; i < l; i++) { + value[i] = (String) a.get(i); + } + + return value; + } catch (JSONException ignore) { + // value was not found + } + + return defaultValue; + } + + /** + * Given a JSON key path, gets the deepest key. + * + * @param key The key path + * @return The deepest key + */ + private static String getDeepestKey(String key) { + String[] parts = key.split("\\."); + if (parts.length > 0) { + return parts[parts.length - 1]; + } + + return null; + } + + /** + * Given a JSON object and key path, gets the deepest object in the path. + * + * @param jsonObject A JSON object + * @param key The key path to follow + * @return The deepest object along the key path + * @throws JSONException Thrown if any JSON errors + */ + private static JSONObject getDeepestObject(JSONObject jsonObject, String key) throws JSONException { + String[] parts = key.split("\\."); + JSONObject o = jsonObject; + + // Search until the second to last part of the key + for (int i = 0; i < parts.length - 1; i++) { + String k = parts[i]; + o = o.getJSONObject(k); + } + + return o; + } +} diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java new file mode 100644 index 000000000..e6579549e --- /dev/null +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java @@ -0,0 +1,104 @@ +package com.getcapacitor; + +import static org.junit.Assert.*; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +public class ConfigBuildingTest { + + final String TEST_PLUGIN_NAME = "TestPlugin"; + + JSONObject pluginConfig = new JSONObject(); + JSONObject testPluginObject = new JSONObject(); + JSONObject testPluginNestedObject = new JSONObject(); + JSONArray testPluginArray = new JSONArray(); + + CapConfig config = null; + + @Before + public void setup() { + try { + testPluginNestedObject.put("var10", true); + + testPluginArray.put("5"); + testPluginArray.put("6"); + testPluginArray.put("7"); + testPluginArray.put("8"); + + testPluginObject.put("var1", true); + testPluginObject.put("var2", "hello"); + testPluginObject.put("var3", testPluginNestedObject); + testPluginObject.put("var4", 2); + testPluginObject.put("var5", testPluginArray); + + pluginConfig.put(TEST_PLUGIN_NAME, testPluginObject); + + config = + new CapConfig.Builder() + .setAllowMixedContent(true) + .setAllowNavigation(new String[] { "http://www.google.com" }) + .setAndroidScheme("test") + .setCaptureInput(true) + .setLogsHidden(true) + .setHTML5mode(false) + .setOverriddenUserAgentString("test-user-agent") + .setAppendedUserAgentString("test-append") + .setWebContentsDebuggingEnabled(true) + .setBackgroundColor("red") + .setPluginsConfiguration(pluginConfig) + .setServerUrl("http://www.google.com") + .create(); + } catch (Exception e) { + fail(); + } + } + + @Test + public void getCoreConfigValues() { + assertTrue(config.isMixedContentAllowed()); + assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation()); + assertEquals("test", config.getAndroidScheme()); + assertTrue(config.isInputCaptured()); + assertTrue(config.isLogsHidden()); + assertFalse(config.isHTML5Mode()); + assertEquals("test-user-agent", config.getOverriddenUserAgentString()); + assertEquals("test-append", config.getAppendedUserAgentString()); + assertTrue(config.isWebContentsDebuggingEnabled()); + assertEquals("red", config.getBackgroundColor()); + assertEquals("http://www.google.com", config.getServerUrl()); + } + + @Test + public void getPluginString() { + String testString = config.getPluginConfiguration(TEST_PLUGIN_NAME).getString("var2"); + assertEquals("hello", testString); + } + + @Test + public void getPluginBoolean() { + boolean testBool = config.getPluginConfiguration(TEST_PLUGIN_NAME).getBoolean("var1", false); + assertTrue(testBool); + } + + @Test + public void getPluginInt() { + int testInt = config.getPluginConfiguration(TEST_PLUGIN_NAME).getInt("var4", -1); + assertEquals(2, testInt); + } + + @Test + public void getPluginArray() { + String[] comparison = new String[] { "5", "6", "7", "8" }; + String[] testArray = config.getPluginConfiguration(TEST_PLUGIN_NAME).getArray("var5"); + assertArrayEquals(comparison, testArray); + } + + @Test + public void getPluginObject() { + JSONObject testObject = config.getPluginConfiguration(TEST_PLUGIN_NAME).getObject("var3"); + assertEquals(testPluginNestedObject, testObject); + } +} diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java new file mode 100644 index 000000000..5f2269c13 --- /dev/null +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java @@ -0,0 +1,116 @@ +package com.getcapacitor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class ConfigReadingTest { + + private static final String FLAT_TEST = "configs/flat.json"; + private static final String BAD_TEST = "configs/bad.json"; + private static final String HIERARCHY_TEST = "configs/hierarchy.json"; + private static final String NONJSON_TEST = "configs/nonjson.json"; + private static final String SERVER_TEST = "configs/server.json"; + + Activity context = Mockito.mock(Activity.class); + AssetManager assetManager = Mockito.mock(AssetManager.class); + ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + + private InputStream getTestInputStream(String testPath) { + return this.getClass().getClassLoader().getResourceAsStream(testPath); + } + + @Before + public void before() { + when(context.getAssets()).thenReturn(assetManager); + when(context.getApplicationInfo()).thenReturn(applicationInfo); + } + + @Test + public void bad() { + try { + when(assetManager.open("capacitor.config.json")).thenReturn(getTestInputStream(BAD_TEST)); + + CapConfig config = CapConfig.fromFile(context); + assertEquals("not a real domain", config.getServerUrl()); + assertNull(config.getBackgroundColor()); + assertFalse(config.isLogsHidden()); + } catch (IOException e) { + fail(); + } + } + + @Test + public void flat() { + try { + when(assetManager.open("capacitor.config.json")).thenReturn(getTestInputStream(FLAT_TEST)); + + CapConfig config = CapConfig.fromFile(context); + assertEquals("level 1 override", config.getOverriddenUserAgentString()); + assertEquals("level 1 append", config.getAppendedUserAgentString()); + assertEquals("#ffffff", config.getBackgroundColor()); + assertTrue(config.isLogsHidden()); + assertEquals(1, config.getPluginConfiguration("SplashScreen").getInt("launchShowDuration", 0)); + } catch (IOException e) { + fail(); + } + } + + @Test + public void hierarchy() { + try { + when(assetManager.open("capacitor.config.json")).thenReturn(getTestInputStream(HIERARCHY_TEST)); + + CapConfig config = CapConfig.fromFile(context); + assertEquals("level 2 override", config.getOverriddenUserAgentString()); + assertEquals("level 2 append", config.getAppendedUserAgentString()); + assertEquals("#000000", config.getBackgroundColor()); + assertFalse(config.isLogsHidden()); + } catch (IOException e) { + fail(); + } + } + + @Test + public void nonJSON() { + try { + final String errText = "Unable to parse capacitor.config.json. Make sure it's valid json"; + + when(assetManager.open("capacitor.config.json")).thenReturn(getTestInputStream(NONJSON_TEST)); + + try (MockedStatic logger = mockStatic(Logger.class)) { + CapConfig config = CapConfig.fromFile(context); + logger.verify(times(1), () -> Logger.error(eq(errText), any())); + } + } catch (IOException e) { + fail(); + } + } + + @Test + public void server() { + try { + when(assetManager.open("capacitor.config.json")).thenReturn(getTestInputStream(SERVER_TEST)); + + CapConfig config = CapConfig.fromFile(context); + assertEquals("myhost", config.getHostname()); + assertEquals("http://192.168.100.1:2057", config.getServerUrl()); + assertEquals("override", config.getAndroidScheme()); + } catch (IOException e) { + fail(); + } + } +} diff --git a/android/capacitor/src/test/resources/configs/bad.json b/android/capacitor/src/test/resources/configs/bad.json new file mode 100644 index 000000000..32d498f4a --- /dev/null +++ b/android/capacitor/src/test/resources/configs/bad.json @@ -0,0 +1,28 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": 0, + "hideLogs": "yep", + "ios": { + "allowsLinkPreview": false, + "scrollEnabled": false, + "contentInset": "what's an axis?" + }, + "server": { + "iosScheme": "http", + "allowNavigation": ["capacitorjs.com", "ionic.io", "192.168.0.1"], + "hostname": "myhost", + "url": "not a real domain" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} \ No newline at end of file diff --git a/android/capacitor/src/test/resources/configs/flat.json b/android/capacitor/src/test/resources/configs/flat.json new file mode 100644 index 000000000..9451307b8 --- /dev/null +++ b/android/capacitor/src/test/resources/configs/flat.json @@ -0,0 +1,17 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "plugins": { + "SplashScreen": { + "launchShowDuration": 1 + } + }, + "cordova": {} +} \ No newline at end of file diff --git a/android/capacitor/src/test/resources/configs/hierarchy.json b/android/capacitor/src/test/resources/configs/hierarchy.json new file mode 100644 index 000000000..aaa7c304c --- /dev/null +++ b/android/capacitor/src/test/resources/configs/hierarchy.json @@ -0,0 +1,26 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "android": { + "overrideUserAgent": "level 2 override", + "appendUserAgent": "level 2 append", + "backgroundColor": "#000000", + "hideLogs": false, + "allowsLinkPreview": false, + "scrollEnabled": false, + "contentInset": "scrollableAxes" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} \ No newline at end of file diff --git a/android/capacitor/src/test/resources/configs/nonjson.json b/android/capacitor/src/test/resources/configs/nonjson.json new file mode 100644 index 000000000..62f00c9f6 --- /dev/null +++ b/android/capacitor/src/test/resources/configs/nonjson.json @@ -0,0 +1 @@ +This is not even JSON. \ No newline at end of file diff --git a/android/capacitor/src/test/resources/configs/server.json b/android/capacitor/src/test/resources/configs/server.json new file mode 100644 index 000000000..8e9b21999 --- /dev/null +++ b/android/capacitor/src/test/resources/configs/server.json @@ -0,0 +1,23 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "server": { + "androidScheme": "override", + "allowNavigation": ["capacitorjs.com", "ionic.io", "192.168.0.1"], + "hostname": "myhost", + "url": "http://192.168.100.1:2057" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} \ No newline at end of file