Bug 1207719 - (Part 2) Change Switchboard to combine network requests for experiments and server configurations into a single network fetch. r=sebastian
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Sun, 03 Apr 2016 17:20:44 -0400
changeset 291732 43138b644a40880ef532d6fb8e43ef16b446d266
parent 291731 0912147b690ebe731de6c976e30cfd821c99ed6c
child 291733 ac533e9899b77da4dcb3683440a789cdf06b4fab
push id30145
push userkwierso@gmail.com
push dateTue, 05 Apr 2016 23:25:37 +0000
treeherdermozilla-central@a235bfcc8c41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1207719
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1207719 - (Part 2) Change Switchboard to combine network requests for experiments and server configurations into a single network fetch. r=sebastian MozReview-Commit-ID: c38pPvDrT8
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java
mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java
mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -812,37 +812,32 @@ public class BrowserApp extends GeckoApp
     private void initSwitchboard(Intent intent) {
         if (Experiments.isDisabled(new SafeIntent(intent)) || !AppConstants.MOZ_SWITCHBOARD) {
             return;
         }
 
         final String hostExtra = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_HOST);
         final String host = TextUtils.isEmpty(hostExtra) ? DEFAULT_SWITCHBOARD_HOST : hostExtra;
 
-        final String configServerUpdateUrl;
-        final String configServerUrl;
+        final String serverUrl;
         try {
-            configServerUpdateUrl = new URL("https", host, "urls").toString();
-            configServerUrl = new URL("https", host, "v1").toString();
+            serverUrl = new URL("https", host, "v2").toString();
         } catch (MalformedURLException e) {
             Log.e(LOGTAG, "Error creating Switchboard server URL", e);
             return;
         }
 
-        SwitchBoard.initDefaultServerUrls(configServerUpdateUrl, configServerUrl, true);
-
         final String switchboardUUID = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_UUID);
         SwitchBoard.setUUIDFromExtra(switchboardUUID);
 
-        // Looks at the server if there are changes in the server URL that should be used in the future
-        new AsyncConfigLoader(this, AsyncConfigLoader.UPDATE_SERVER, switchboardUUID).execute();
-
-        // Loads the actual config. This can be done on app start or on app onResume() depending
-        // how often you want to update the config.
-        new AsyncConfigLoader(this, AsyncConfigLoader.CONFIG_SERVER, switchboardUUID).execute();
+        // Loads the Switchboard config from the specified server URL. Eventually, we
+        // should use the endpoint returned by the server URL, to support migrating
+        // to a new endpoint. However, if we want to do that, we'll need to find a different
+        // solution for dynamically changing the server URL from the intent.
+        new AsyncConfigLoader(this, switchboardUUID, serverUrl).execute();
     }
 
     private void showUpdaterPermissionSnackbar() {
         SnackbarHelper.SnackbarCallback allowCallback = new SnackbarHelper.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 Permissions.from(BrowserApp.this)
                         .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java
+++ b/mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java
@@ -13,72 +13,45 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 */
 package com.keepsafe.switchboard;
 
 
 import android.content.Context;
 import android.os.AsyncTask;
-import android.util.Log;
 
 /**
  * An async loader to load user config in background thread based on internal generated UUID.
  * 
  * Call <code>AsyncConfigLoader.execute()</code> to load SwitchBoard.loadConfig() with own ID. 
  * To use your custom UUID call <code>AsyncConfigLoader.execute(uuid)</code> with uuid being your unique user id
  * as a String 
  *
  * @author Philipp Berner
  *
  */
 public class AsyncConfigLoader extends AsyncTask<Void, Void, Void> {
 
-    private String TAG = "AsyncConfigLoader";
-
-    public static final int UPDATE_SERVER = 1;
-    public static final int CONFIG_SERVER = 2;
-
     private Context context;
-    private int configToLoad;
     private String uuid;
-
-    /**
-     * Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
-     * or SwitchBoard.loadConfig.
-     * @param c Application context
-     * @param configType Either UPDATE_SERVER or CONFIG_SERVER
-     */
-    public AsyncConfigLoader(Context c, int configType) {
-        this(c, configType, null);
-    }
+    private String defaultServerUrl;
 
     /**
      * Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
      * or SwitchBoard.loadConfig.
      * Loads config with a custom UUID
      * @param c Application context
-     * @param configType Either UPDATE_SERVER or CONFIG_SERVER
      * @param uuid Custom UUID
+     * @param defaultServerUrl Default URL endpoint for Switchboard config.
      */
-    public AsyncConfigLoader(Context c, int configType, String uuid) {
+    public AsyncConfigLoader(Context c, String uuid, String defaultServerUrl) {
         this.context = c;
-        this.configToLoad = configType;
         this.uuid = uuid;
+        this.defaultServerUrl = defaultServerUrl;
     }
 
     @Override
     protected Void doInBackground(Void... params) {
-
-        if(configToLoad == UPDATE_SERVER) {
-            SwitchBoard.updateConfigServerUrl(context);
-        }
-        else {
-            if(uuid == null)
-                SwitchBoard.loadConfig(context);
-            else
-                SwitchBoard.loadConfig(context, uuid);
-        }
-
+        SwitchBoard.loadConfig(context, uuid, defaultServerUrl);
         return null;
     }
-
-}
\ No newline at end of file
+}
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java
+++ b/mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java
@@ -13,104 +13,66 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 */
 package com.keepsafe.switchboard;
 
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
+import android.support.annotation.Nullable;
 
 /**
  * Application preferences for SwitchBoard.
  * @author Philipp Berner
  *
  */
 public class Preferences {
-    private static final String TAG = "Preferences";
 
     private static final String switchBoardSettings = "com.keepsafe.switchboard.settings";
 
-    //dynamic config
     private static final String kDynamicConfigServerUrl = "dynamic-config-server-url";
-    private static final String kDynamicConfigServerUpdateUrl = "dynamic-config-server-update-url";
     private static final String kDynamicConfig = "dynamic-config";
 
-
-
-    //dynamic config
-    /** TODO check this!!!
-     * Returns a JSON string array with <br />
-     * position 0 = updateserverUrl <br />
-     * Fields a null if not existent.
-     * @param c
-     * @return
+    /**
+     * Returns the stored config server URL.
+     * @param c Context
+     * @return URL for config endpoint.
      */
-    public static String getDynamicUpdateServerUrl(Context c) {
-        SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
-        return settings.getString(kDynamicConfigServerUpdateUrl, null);
-    }
-
-    /**
-     * Returns a JSON string array with <br />
-     * postiion 1 = configServerUrl <br />
-     * Fields a null if not existent.
-     * @param c
-     * @return
-     */
-    public static String getDynamicConfigServerUrl(Context c) {
-        SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
-        return settings.getString(kDynamicConfigServerUrl, null);
+    @Nullable public static String getDynamicConfigServerUrl(Context c) {
+        final SharedPreferences prefs = c.getApplicationContext().getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
+        return prefs.getString(kDynamicConfigServerUrl, null);
     }
 
     /**
      * Stores the config servers URL.
-     * @param c
-     * @param updateServerUrl Url end point to get the current config server location
-     * @param configServerUrl UR: end point to get the current endpoint for the apps config file
-     * @return true if saved successful
+     * @param c Context
+     * @param configServerUrl URL for config endpoint.
      */
-    public static boolean setDynamicConfigServerUrl(Context c, String updateServerUrl, String configServerUrl) {
-
-        SharedPreferences.Editor settings = (Editor) Preferences.getPreferenceObject(c, true);
-        settings.putString(kDynamicConfigServerUpdateUrl, updateServerUrl);
-        settings.putString(kDynamicConfigServerUrl, configServerUrl);
-        return settings.commit();
+    public static void setDynamicConfigServerUrl(Context c, String configServerUrl) {
+        final SharedPreferences.Editor editor = c.getApplicationContext().
+                getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
+        editor.putString(kDynamicConfigServerUrl, configServerUrl);
+        editor.apply();
     }
 
     /**
      * Gets the user config as a JSON string.
-     * @param c
-     * @return
+     * @param c Context
+     * @return Config JSON
      */
-    public static String getDynamicConfigJson(Context c) {
-        SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
-        return settings.getString(kDynamicConfig, null);
+    @Nullable public static String getDynamicConfigJson(Context c) {
+        final SharedPreferences prefs = c.getApplicationContext().getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
+        return prefs.getString(kDynamicConfig, null);
     }
 
     /**
      * Saves the user config as a JSON sting.
-     * @param c
-     * @param configJson
-     * @return
+     * @param c Context
+     * @param configJson Config JSON
      */
-    public static boolean setDynamicConfigJson(Context c, String configJson) {
-        SharedPreferences.Editor settings = (Editor) Preferences.getPreferenceObject(c, true);
-        settings.putString(kDynamicConfig, configJson);
-        return settings.commit();
-    }
-
-    static private Object getPreferenceObject(Context ctx, boolean writeable) {
-
-        Object returnValue = null;
-
-        Context sharedDelegate = ctx.getApplicationContext();
-
-        if(!writeable) {
-            returnValue = sharedDelegate.getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
-        } else {
-            returnValue = sharedDelegate.getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
-        }
-
-        return returnValue;
+    public static void setDynamicConfigJson(Context c, String configJson) {
+        final SharedPreferences.Editor editor = c.getApplicationContext().
+                getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
+        editor.putString(kDynamicConfig, configJson);
+        editor.apply();
     }
 }
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
+++ b/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
@@ -15,35 +15,36 @@
 */
 package com.keepsafe.switchboard;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
-import java.net.ProtocolException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.zip.CRC32;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
-import android.support.v4.content.LocalBroadcastManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
+
 /**
  * SwitchBoard is the core class of the KeepSafe Switchboard mobile A/B testing framework.
  * This class provides a bunch of static methods that can be used in your app to run A/B tests. 
  * 
  * The SwitchBoard supports production and staging environment. 
  * 
  * For usage <code>initDefaultServerUrls</code> for first time usage. Server URLs can be updates from
  * a remote location with <code>initConfigServerUrl</code>.
@@ -55,198 +56,115 @@ import android.util.Log;
  * 
  * @author Philipp Berner
  *
  */
 public class SwitchBoard {
 
     private static final String TAG = "SwitchBoard";
 
-    /** Set if the application is run in debug mode. DynamicConfig runs against staging server when in debug and production when not */
+    /** Set if the application is run in debug mode. */
     public static boolean DEBUG = true;
 
-    /** Production server to update the remote server URLs. http://staging.domain/path_to/SwitchboardURLs.php */
-    private static String DYNAMIC_CONFIG_SERVER_URL_UPDATE;
-
-    /** Production server for getting the actual config file. http://staging.domain/path_to/SwitchboardDriver.php */
-    private static String DYNAMIC_CONFIG_SERVER_DEFAULT_URL;
-
-    public static final String ACTION_CONFIG_FETCHED = ".SwitchBoard.CONFIG_FETCHED";
-
-    private static final String kUpdateServerUrl = "updateServerUrl";
-    private static final String kConfigServerUrl = "configServerUrl";
-
     private static final String IS_EXPERIMENT_ACTIVE = "isActive";
     private static final String EXPERIMENT_VALUES = "values";
 
-    private static String uuidExtra = null;
-
+    private static final String KEY_SERVER_URL = "mainServerUrl";
+    private static final String KEY_CONFIG_RESULTS = "results";
 
-    /**
-     * Basic initialization with one server.
-     * @param configServerUpdateUrl Url to: http://staging.domain/path_to/SwitchboardURLs.php
-     * @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php - the acutall config
-     * @param isDebug Is the application running in debug mode. This will add log messages.
-     */
-    public static void initDefaultServerUrls(String configServerUpdateUrl, String configServerUrl,
-            boolean isDebug) {
-
-        DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrl;
-        DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrl;
-        DEBUG = isDebug;
-    }
+    private static String uuidExtra = null;
 
     public static void setUUIDFromExtra(String uuid) {
         uuidExtra = uuid;
     }
-    /**
-     * Advanced initialization that supports a production and staging environment without changing the server URLs manually.
-     * SwitchBoard will connect to the staging environment in debug mode. This makes it very simple to test new experiements
-     * during development.
-     * @param configServerUpdateUrlStaging Url to http://staging.domain/path_to/SwitchboardURLs.php in staging environment
-     * @param configServerUrlStaging Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
-     * @param configServerUpdateUrl Url to http://staging.domain/path_to/SwitchboardURLs.php in production environment
-     * @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
-     * @param isDebug Defines if the app runs in debug.
-     */
-    public static void initDefaultServerUrls(String configServerUpdateUrlStaging, String configServerUrlStaging,
-            String configServerUpdateUrl, String configServerUrl,
-            boolean isDebug) {
-
-        if(isDebug) {
-            DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrlStaging;
-            DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrlStaging;
-        } else {
-            DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrl;
-            DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrl;
-        }
-
-        DEBUG = isDebug;
-    }
-
-    /**
-     * Updates the server URLs from remote and stores it locally in the app. This allows to move the server side
-     * whith users already using Switchboard.
-     * When there is no internet connection it will continue to use the URLs from the last time or
-     * default URLS that have been set with <code>initDefaultServerUrls</code>.
-     *
-     * This methode should always be executed in a background thread to not block the UI.
-     *
-     * @param c Application context
-     */
-    public static void updateConfigServerUrl(Context c) {
-        if(DEBUG) Log.d(TAG, "start initConfigServerUrl");
-
-        if(DEBUG) {
-            //set default value that is set in code for debug mode.
-            Preferences.setDynamicConfigServerUrl(c, DYNAMIC_CONFIG_SERVER_URL_UPDATE, DYNAMIC_CONFIG_SERVER_DEFAULT_URL);
-            return;
-        }
-
-        //lookup new config server url from the one that is in shared prefs
-        String updateServerUrl = Preferences.getDynamicUpdateServerUrl(c);
-
-        //set to default when not set in preferences
-        if(updateServerUrl == null)
-            updateServerUrl = DYNAMIC_CONFIG_SERVER_URL_UPDATE;
-
-        try {
-            String result = readFromUrlGET(updateServerUrl, "");
-            if(DEBUG) Log.d(TAG, "Result String: " + result);
-
-            if(result != null){
-                JSONObject a = new JSONObject(result);
-
-                Preferences.setDynamicConfigServerUrl(c, (String)a.get(kUpdateServerUrl), (String)a.get(kConfigServerUrl));
-
-                if(DEBUG) Log.d(TAG, "Update Server Url: " + (String)a.get(kUpdateServerUrl));
-                if(DEBUG) Log.d(TAG, "Config Server Url: " + (String)a.get(kConfigServerUrl));
-            } else {
-                storeDefaultUrlsInPreferences(c);
-            }
-
-        } catch (JSONException e) {
-            e.printStackTrace();
-        }
-
-        if(DEBUG) Log.d(TAG, "end initConfigServerUrl");
-    }
-
-    /**
-     * Loads a new config file for the specific user from current config server. Uses internal unique user ID.
-     * Use this method only in background thread as network connections are involved that block UI thread.
-     * Use AsyncConfigLoader() for easy background threading.
-     * @param c ApplicationContext
-     */
-    public static void loadConfig(Context c) {
-        loadConfig(c, null);
-    }
 
     /**
      * Loads a new config for a user. This method allows you to pass your own unique user ID instead of using
      * the SwitchBoard internal user ID.
      * Don't call method direct for background threading reasons.
      * @param c ApplicationContext
      * @param uuid Custom unique user ID
+     * @param defaultServerUrl Default server URL endpoint.
      */
-    public static void loadConfig(Context c, String uuid) {
+    static void loadConfig(Context c, String uuid, @NonNull String defaultServerUrl) {
+
+        // Eventually, we want to check `Preferences.getDynamicConfigServerUrl(c);` before
+        // falling back to the default server URL. However, this will require figuring
+        // out a new solution for dynamically specifying a new server from the intent.
+        String serverUrl = defaultServerUrl;
+
+        final URL requestUrl = buildConfigRequestUrl(c, uuid, serverUrl);
+        if (requestUrl == null) {
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, requestUrl.toString());
+
+        final String result = readFromUrlGET(requestUrl);
+        if (DEBUG) Log.d(TAG, result);
+
+        if (result == null) {
+            return;
+        }
 
         try {
+            final JSONObject json = new JSONObject(result);
 
-            //get uuid
-            if(uuid == null) {
-                DeviceUuidFactory df = new DeviceUuidFactory(c);
-                uuid = df.getDeviceUuid().toString();
+            // Update the server URL if necessary.
+            final String newServerUrl = json.getString(KEY_SERVER_URL);
+            if (!defaultServerUrl.equals(newServerUrl)) {
+                Preferences.setDynamicConfigServerUrl(c, newServerUrl);
             }
 
-            String device = Build.DEVICE;
-            String manufacturer = Build.MANUFACTURER;
-            String lang = "unknown";
-            try {
-                lang = Locale.getDefault().getISO3Language();
-            } catch (MissingResourceException e) {
-                e.printStackTrace();
-            }
-            String country = "unknown";
-            try {
-                country = Locale.getDefault().getISO3Country();
-            } catch (MissingResourceException e) {
-                e.printStackTrace();
-            }
-            String packageName = c.getPackageName();
-            String versionName = "none";
-            try {
-                versionName = c.getPackageManager().getPackageInfo(c.getPackageName(), 0).versionName;
-            } catch (NameNotFoundException e) {
-                e.printStackTrace();
-            }
+            // Store the config in shared prefs.
+            final String config = json.getString(KEY_CONFIG_RESULTS);
+            Preferences.setDynamicConfigJson(c, config);
+        } catch (JSONException e) {
+            Log.e(TAG, "Exception parsing server result", e);
+        }
+    }
 
-            //load config, includes all experiments
-            String serverUrl = Preferences.getDynamicConfigServerUrl(c);
-
-            if(serverUrl != null) {
-                String params = "uuid="+uuid+"&device="+device+"&lang="+lang+"&country="+country
-                        +"&manufacturer="+manufacturer+"&appId="+packageName+"&version="+versionName;
-                if(DEBUG) Log.d(TAG, "Read from server URL: " + serverUrl + "?" + params);
-                String serverConfig = readFromUrlGET(serverUrl, params);
+    @Nullable private static URL buildConfigRequestUrl(Context c, String uuid, String serverUrl) {
+        if (uuid == null) {
+            DeviceUuidFactory df = new DeviceUuidFactory(c);
+            uuid = df.getDeviceUuid().toString();
+        }
 
-                if(DEBUG) Log.d(TAG, serverConfig);
-
-                //store experiments in shared prefs (one variable)
-                if(serverConfig != null)
-                    Preferences.setDynamicConfigJson(c, serverConfig);
-            }
-
-        } catch (NullPointerException e) {
+        final String device = Build.DEVICE;
+        final String manufacturer = Build.MANUFACTURER;
+        String lang = "unknown";
+        try {
+            lang = Locale.getDefault().getISO3Language();
+        } catch (MissingResourceException e) {
+            e.printStackTrace();
+        }
+        String country = "unknown";
+        try {
+            country = Locale.getDefault().getISO3Country();
+        } catch (MissingResourceException e) {
             e.printStackTrace();
         }
 
-        //notify listeners that the config fetch has completed
-        Intent i = new Intent(ACTION_CONFIG_FETCHED);
-        LocalBroadcastManager.getInstance(c).sendBroadcast(i);
+        final String packageName = c.getPackageName();
+        String versionName = "none";
+        try {
+            versionName = c.getPackageManager().getPackageInfo(c.getPackageName(), 0).versionName;
+        } catch (NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        final String params = "uuid="+uuid+"&device="+device+"&lang="+lang+"&country="+country
+                +"&manufacturer="+manufacturer+"&appId="+packageName+"&version="+versionName;
+
+        try {
+            return new URL(serverUrl + "?" + params);
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+            return null;
+        }
     }
 
     public static boolean isInBucket(Context c, int low, int high) {
         int userBucket = getUserBucket(c);
         if (userBucket >= low && userBucket < high)
             return true;
         else
             return false;
@@ -369,69 +287,42 @@ public class SwitchBoard {
             e.printStackTrace();
             Log.e(TAG, "Could not create JSON object from config string", e);
         }
 
         return null;
     }
 
     /**
-     * Sets config server URLs in shared prefs to defaul when not set already. It keeps
-     * URLs when already set in shared preferences.
-     * @param c
-     */
-    private static void storeDefaultUrlsInPreferences(Context c) {
-        String configUrl = Preferences.getDynamicConfigServerUrl(c);
-        String updateUrl = Preferences.getDynamicUpdateServerUrl(c);
-
-        if(configUrl == null)
-            configUrl = DYNAMIC_CONFIG_SERVER_DEFAULT_URL;
-
-        if(updateUrl == null)
-            updateUrl = DYNAMIC_CONFIG_SERVER_URL_UPDATE;
-
-        Preferences.setDynamicConfigServerUrl(c, updateUrl, configUrl);
-    }
-
-    /**
      * Returns a String containing the server response from a GET request
-     * @param address Valid http addess.
-     * @param params String of params. Multiple params seperated with &. No leading ? in string
+     * @param url URL for GET request.
      * @return Returns String from server or null when failed.
      */
-    private static String readFromUrlGET(String address, String params) {
-        if(address == null || params == null)
-            return null;
-
-        String completeUrl = address + "?" + params;
-        if(DEBUG) Log.d(TAG, "readFromUrl(): " + completeUrl);
+    @Nullable private static String readFromUrlGET(URL url) {
+        if (DEBUG) Log.d(TAG, "readFromUrl(): " + url);
 
         try {
-            URL url = new URL(completeUrl);
             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
             connection.setRequestMethod("GET");
             connection.setUseCaches(false);
 
-            // get response
             InputStream is = connection.getInputStream();
             InputStreamReader inputStreamReader = new InputStreamReader(is);
             BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192);
             String line = "";
-            StringBuffer resultContent = new StringBuffer();
+            StringBuilder resultContent = new StringBuilder();
             while ((line = bufferReader.readLine()) != null) {
                 if(DEBUG) Log.d(TAG, line);
                 resultContent.append(line);
             }
             bufferReader.close();
 
             if(DEBUG) Log.d(TAG, "readFromUrl() result: " + resultContent.toString());
 
             return resultContent.toString();
-        } catch (ProtocolException e) {
-            e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         }
 
         return null;
     }
 
     /**