content: package com.getcapacitor;
  
  import static com.getcapacitor.Bridge.CAPACITOR_HTTPS_SCHEME;
  import static com.getcapacitor.Bridge.DEFAULT_ANDROID_WEBVIEW_VERSION;
  import static com.getcapacitor.Bridge.DEFAULT_HUAWEI_WEBVIEW_VERSION;
  import static com.getcapacitor.Bridge.MINIMUM_ANDROID_WEBVIEW_VERSION;
  import static com.getcapacitor.Bridge.MINIMUM_HUAWEI_WEBVIEW_VERSION;
  import static com.getcapacitor.FileUtils.readFileFromAssets;
  
  import android.content.Context;
  import android.content.pm.ApplicationInfo;
  import android.content.res.AssetManager;
  import androidx.annotation.Nullable;
  import com.getcapacitor.util.JSONUtils;
  import java.io.File;
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  /**
   * Represents the configuration options for Capacitor
   */
  public class CapConfig {
  
      private static final String LOG_BEHAVIOR_NONE = "none";
      private static final String LOG_BEHAVIOR_DEBUG = "debug";
      private static final String LOG_BEHAVIOR_PRODUCTION = "production";
  
      // Server Config
      private boolean html5mode = true;
      private String serverUrl;
      private String hostname = "localhost";
      private String androidScheme = CAPACITOR_HTTPS_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 loggingEnabled = true;
      private boolean initialFocus = true;
      private boolean useLegacyBridge = false;
      private int minWebViewVersion = DEFAULT_ANDROID_WEBVIEW_VERSION;
      private int minHuaweiWebViewVersion = DEFAULT_HUAWEI_WEBVIEW_VERSION;
      private String errorPath;
      private boolean zoomableWebView = false;
  
      // Embedded
      private String startPath;
  
      // Plugins
      private Map<String, PluginConfig> pluginsConfiguration = null;
  
      // Config Object JSON (legacy)
      private JSONObject configJSON = new JSONObject();
  
      /**
       * Constructs an empty config file.
       */
      private CapConfig() {}
  
      /**
       * Get an instance of the Config file object.
       * @deprecated use {@link #loadDefault(Context)} to load an instance of the Config object
       * from the capacitor.config.json file, or use the {@link CapConfig.Builder} to construct
       * a CapConfig for embedded use.
       *
       * @param assetManager The AssetManager used to load the config file
       * @param config JSON describing a configuration to use
       */
      @Deprecated
      public CapConfig(AssetManager assetManager, JSONObject config) {
          if (config != null) {
              this.configJSON = config;
          } else {
              // Load the capacitor.config.json
              loadConfigFromAssets(assetManager, null);
          }
  
          deserializeConfig(null);
      }
  
      /**
       * Constructs a Capacitor Configuration from config.json file.
       *
       * @param context The context.
       * @return A loaded config file, if successful.
       */
      public static CapConfig loadDefault(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.loadConfigFromAssets(context.getAssets(), null);
          config.deserializeConfig(context);
          return config;
      }
  
      /**
       * Constructs a Capacitor Configuration from config.json file within the app assets.
       *
       * @param context The context.
       * @param path A path relative to the root assets directory.
       * @return A loaded config file, if successful.
       */
      public static CapConfig loadFromAssets(Context context, String path) {
          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.loadConfigFromAssets(context.getAssets(), path);
          config.deserializeConfig(context);
          return config;
      }
  
      /**
       * Constructs a Capacitor Configuration from config.json file within the app file-space.
       *
       * @param context The context.
       * @param path A path relative to the root of the app file-space.
       * @return A loaded config file, if successful.
       */
      public static CapConfig loadFromFile(Context context, String path) {
          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.loadConfigFromFile(path);
          config.deserializeConfig(context);
          return config;
      }
  
      /**
       * 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;
  
          if (this.validateScheme(builder.androidScheme)) {
              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.loggingEnabled = builder.loggingEnabled;
          this.initialFocus = builder.initialFocus;
          this.useLegacyBridge = builder.useLegacyBridge;
          this.minWebViewVersion = builder.minWebViewVersion;
          this.minHuaweiWebViewVersion = builder.minHuaweiWebViewVersion;
          this.errorPath = builder.errorPath;
          this.zoomableWebView = builder.zoomableWebView;
  
          // Embedded
          this.startPath = builder.startPath;
  
          // Plugins Config
          this.pluginsConfiguration = builder.pluginsConfiguration;
      }
  
      /**
       * Loads a Capacitor Configuration JSON file into a Capacitor Configuration object.
       * An optional path string can be provided to look for the config in a subdirectory path.
       */
      private void loadConfigFromAssets(AssetManager assetManager, String path) {
          if (path == null) {
              path = "";
          } else {
              // Add slash at the end to form a proper file path if going deeper in assets dir
              if (path.charAt(path.length() - 1) != '/') {
                  path = path + "/";
              }
          }
  
          try {
              String jsonString = readFileFromAssets(assetManager, path + "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) {
              Logger.error("Unable to parse capacitor.config.json. Make sure it's valid json", ex);
          }
      }
  
      /**
       * Loads a Capacitor Configuration JSON file into a Capacitor Configuration object.
       * An optional path string can be provided to look for the config in a subdirectory path.
       */
      private void loadConfigFromFile(String path) {
          if (path == null) {
              path = "";
          } else {
              // Add slash at the end to form a proper file path if going deeper in assets dir
              if (path.charAt(path.length() - 1) != '/') {
                  path = path + "/";
              }
          }
  
          try {
              File configFile = new File(path + "capacitor.config.json");
              String jsonString = FileUtils.readFileFromDisk(configFile);
              configJSON = new JSONObject(jsonString);
          } catch (JSONException ex) {
              Logger.error("Unable to parse capacitor.config.json. Make sure it's valid json", ex);
          } catch (IOException ex) {
              Logger.error("Unable to load capacitor.config.json.", ex);
          }
      }
  
      /**
       * Deserializes the config from JSON into a Capacitor Configuration object.
       */
      private void deserializeConfig(@Nullable Context context) {
          boolean isDebug = context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
  
          // Server
          html5mode = JSONUtils.getBoolean(configJSON, "server.html5mode", html5mode);
          serverUrl = JSONUtils.getString(configJSON, "server.url", null);
          hostname = JSONUtils.getString(configJSON, "server.hostname", hostname);
          errorPath = JSONUtils.getString(configJSON, "server.errorPath", null);
  
          String configSchema = JSONUtils.getString(configJSON, "server.androidScheme", androidScheme);
          if (this.validateScheme(configSchema)) {
              androidScheme = configSchema;
          }
  
          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)
              );
          minWebViewVersion = JSONUtils.getInt(configJSON, "android.minWebViewVersion", DEFAULT_ANDROID_WEBVIEW_VERSION);
          minHuaweiWebViewVersion = JSONUtils.getInt(configJSON, "android.minHuaweiWebViewVersion", DEFAULT_HUAWEI_WEBVIEW_VERSION);
          captureInput = JSONUtils.getBoolean(configJSON, "android.captureInput", captureInput);
          useLegacyBridge = JSONUtils.getBoolean(configJSON, "android.useLegacyBridge", useLegacyBridge);
          webContentsDebuggingEnabled = JSONUtils.getBoolean(configJSON, "android.webContentsDebuggingEnabled", isDebug);
          zoomableWebView = JSONUtils.getBoolean(configJSON, "android.zoomEnabled", JSONUtils.getBoolean(configJSON, "zoomEnabled", false));
  
          String logBehavior = JSONUtils.getString(
              configJSON,
              "android.loggingBehavior",
              JSONUtils.getString(configJSON, "loggingBehavior", LOG_BEHAVIOR_DEBUG)
          );
          switch (logBehavior.toLowerCase(Locale.ROOT)) {
              case LOG_BEHAVIOR_PRODUCTION:
                  loggingEnabled = true;
                  break;
              case LOG_BEHAVIOR_NONE:
                  loggingEnabled = false;
                  break;
              default: // LOG_BEHAVIOR_DEBUG
                  loggingEnabled = isDebug;
          }
  
          initialFocus = JSONUtils.getBoolean(configJSON, "android.initialFocus", initialFocus);
  
          // Plugins
          pluginsConfiguration = deserializePluginsConfig(JSONUtils.getObject(configJSON, "plugins"));
      }
  
      private boolean validateScheme(String scheme) {
          List<String> invalidSchemes = Arrays.asList("file", "ftp", "ftps", "ws", "wss", "about", "blob", "data");
          if (invalidSchemes.contains(scheme)) {
              Logger.warn(scheme + " is not an allowed scheme.  Defaulting to https.");
              return false;
          }
  
          // Non-http(s) schemes are not allowed to modify the URL path as of Android Webview 117
          if (!scheme.equals("http") && !scheme.equals("https")) {
              Logger.warn(
                  "Using a non-standard scheme: " + scheme + " for Android. This is known to cause issues as of Android Webview 117."
              );
          }
  
          return true;
      }
  
      public boolean isHTML5Mode() {
          return html5mode;
      }
  
      public String getServerUrl() {
          return serverUrl;
      }
  
      public String getErrorPath() {
          return errorPath;
      }
  
      public String getHostname() {
          return hostname;
      }
  
      public String getStartPath() {
          return startPath;
      }
  
      public String getAndroidScheme() {
          return androidScheme;
      }
  
      public String[] getAllowNavigation() {
          return allowNavigation;
      }
  
      public String getOverriddenUserAgentString() {
          return overriddenUserAgentString;
      }
  
      public String getAppendedUserAgentString() {
          return appendedUserAgentString;
      }
  
      public String getBackgroundColor() {
          return backgroundColor;
      }
  
      public boolean isMixedContentAllowed() {
          return allowMixedContent;
      }
  
      public boolean isInputCaptured() {
          return captureInput;
      }
  
      public boolean isWebContentsDebuggingEnabled() {
          return webContentsDebuggingEnabled;
      }
  
      public boolean isZoomableWebView() {
          return zoomableWebView;
      }
  
      public boolean isLoggingEnabled() {
          return loggingEnabled;
      }
  
      public boolean isInitialFocus() {
          return initialFocus;
      }
  
      public boolean isUsingLegacyBridge() {
          return useLegacyBridge;
      }
  
      public int getMinWebViewVersion() {
          if (minWebViewVersion < MINIMUM_ANDROID_WEBVIEW_VERSION) {
              Logger.warn("Specified minimum webview version is too low, defaulting to " + MINIMUM_ANDROID_WEBVIEW_VERSION);
              return MINIMUM_ANDROID_WEBVIEW_VERSION;
          }
  
          return minWebViewVersion;
      }
  
      public int getMinHuaweiWebViewVersion() {
          if (minHuaweiWebViewVersion < MINIMUM_HUAWEI_WEBVIEW_VERSION) {
              Logger.warn("Specified minimum Huawei webview version is too low, defaulting to " + MINIMUM_HUAWEI_WEBVIEW_VERSION);
              return MINIMUM_HUAWEI_WEBVIEW_VERSION;
          }
  
          return minHuaweiWebViewVersion;
      }
  
      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 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) {
          return JSONUtils.getArray(configJSON, key, defaultValue);
      }
  
      private static Map<String, PluginConfig> deserializePluginsConfig(JSONObject pluginsConfig) {
          Map<String, PluginConfig> pluginsMap = new HashMap<>();
  
          // return an empty map if there is no pluginsConfig json
          if (pluginsConfig == null) {
              return pluginsMap;
          }
  
          Iterator<String> 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 pluginsMap;
      }
  
      /**
       * Builds a Capacitor Configuration in code
       */
      public static class Builder {
  
          private Context context;
  
          // Server Config Values
          private boolean html5mode = true;
          private String serverUrl;
          private String errorPath;
          private String hostname = "localhost";
          private String androidScheme = CAPACITOR_HTTPS_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 = null;
          private boolean loggingEnabled = true;
          private boolean initialFocus = false;
          private boolean useLegacyBridge = false;
          private int minWebViewVersion = DEFAULT_ANDROID_WEBVIEW_VERSION;
          private int minHuaweiWebViewVersion = DEFAULT_HUAWEI_WEBVIEW_VERSION;
          private boolean zoomableWebView = false;
  
          // Embedded
          private String startPath = null;
  
          // Plugins Config Object
          private Map<String, PluginConfig> pluginsConfiguration = new HashMap<>();
  
          /**
           * Constructs a new CapConfig Builder.
           *
           * @param context The context
           */
          public Builder(Context context) {
              this.context = context;
          }
  
          /**
           * Builds a Capacitor Config from the builder.
           *
           * @return A new Capacitor Config
           */
          public CapConfig create() {
              if (webContentsDebuggingEnabled == null) {
                  webContentsDebuggingEnabled = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
              }
  
              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 setErrorPath(String errorPath) {
              this.errorPath = errorPath;
              return this;
          }
  
          public Builder setHostname(String hostname) {
              this.hostname = hostname;
              return this;
          }
  
          public Builder setStartPath(String path) {
              this.startPath = path;
              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 setUseLegacyBridge(boolean useLegacyBridge) {
              this.useLegacyBridge = useLegacyBridge;
              return this;
          }
  
          public Builder setWebContentsDebuggingEnabled(boolean webContentsDebuggingEnabled) {
              this.webContentsDebuggingEnabled = webContentsDebuggingEnabled;
              return this;
          }
  
          public Builder setZoomableWebView(boolean zoomableWebView) {
              this.zoomableWebView = zoomableWebView;
              return this;
          }
  
          public Builder setLoggingEnabled(boolean enabled) {
              this.loggingEnabled = enabled;
              return this;
          }
  
          public Builder setInitialFocus(boolean focus) {
              this.initialFocus = focus;
              return this;
          }
      }
  }
  