Bug 1207719 - (Part 2) Change Switchboard to combine network requests for experiments and server configurations into a single network fetch. r=sebastian draft
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Sun, 03 Apr 2016 17:20:44 -0400
changeset 347225 6ada4aeb9ce898057f6d66ac95b63bf8606abb21
parent 347224 bd6ae3033f25cb8df1e1c995f240368b99f1ae7a
child 347226 32030b578478639907541689b4f6af97793ffc0a
push id14517
push usermleibovic@mozilla.com
push dateSun, 03 Apr 2016 21:27:42 +0000
reviewerssebastian
bugs1207719
milestone48.0a1
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;
     }
 
     /**