diff options
Diffstat (limited to 'mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java')
-rw-r--r-- | mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java | 390 |
1 files changed, 0 insertions, 390 deletions
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java b/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java deleted file mode 100644 index e99144045..000000000 --- a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - Copyright 2012 KeepSafe Software Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -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.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 org.json.JSONArray; - -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Build; -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>. - * - * To run a experiment use <code>isInExperiment()</code>. The experiment name has to match the one you - * setup on the server. - * All functions are design to be safe for programming mistakes and network connection issues. If the - * experiment does not exists it will return false and pretend the user is not part of it. - * - * @author Philipp Berner - * - */ -public class SwitchBoard { - - private static final String TAG = "SwitchBoard"; - - /** Set if the application is run in debug mode. */ - public static boolean DEBUG = true; - - // Top-level experiment keys. - private static final String KEY_DATA = "data"; - private static final String KEY_NAME = "name"; - private static final String KEY_MATCH = "match"; - private static final String KEY_BUCKETS = "buckets"; - private static final String KEY_VALUES = "values"; - - // Match keys. - private static final String KEY_APP_ID = "appId"; - private static final String KEY_COUNTRY = "country"; - private static final String KEY_DEVICE = "device"; - private static final String KEY_LANG = "lang"; - private static final String KEY_MANUFACTURER = "manufacturer"; - private static final String KEY_VERSION = "version"; - - // Bucket keys. - private static final String KEY_MIN = "min"; - private static final String KEY_MAX = "max"; - - /** - * Loads a new config for a user. This method does network I/O, so it - * should not be called on the main thread. - * - * @param c ApplicationContext - * @param serverUrl Server URL endpoint. - */ - static void loadConfig(Context c, @NonNull String serverUrl) { - final URL url; - try { - url = new URL(serverUrl); - } catch (MalformedURLException e) { - Log.e(TAG, "Exception creating server URL", e); - return; - } - - final String result = readFromUrlGET(url); - if (DEBUG) Log.d(TAG, "Result: " + result); - if (result == null) { - return; - } - - // Cache result locally in shared preferences. - Preferences.setDynamicConfigJson(c, result); - } - - public static boolean isInBucket(Context c, int low, int high) { - final int userBucket = getUserBucket(c); - return (userBucket >= low) && (userBucket < high); - } - - /** - * Looks up in config if user is in certain experiment. Returns false as a default value when experiment - * does not exist. - * Experiment names are defined server side as Key in array for return values. - * @param experimentName Name of the experiment to lookup - * @return returns value for experiment or false if experiment does not exist. - */ - public static boolean isInExperiment(Context c, String experimentName) { - final Boolean override = Preferences.getOverrideValue(c, experimentName); - if (override != null) { - return override; - } - - final String config = Preferences.getDynamicConfigJson(c); - if (config == null) { - return false; - } - - try { - // TODO: cache the array into a mapping so we don't do a loop everytime we are looking for a experiment key - final JSONArray experiments = new JSONObject(config).getJSONArray(KEY_DATA); - JSONObject experiment = null; - - for (int i = 0; i < experiments.length(); i++) { - JSONObject entry = experiments.getJSONObject(i); - final String name = entry.getString(KEY_NAME); - if (name.equals(experimentName)) { - experiment = entry; - break; - } - } - - if (experiment == null) { - return false; - } - - if (!isMatch(c, experiment.optJSONObject(KEY_MATCH))) { - return false; - } - - final JSONObject buckets = experiment.getJSONObject(KEY_BUCKETS); - final boolean inExperiment = isInBucket(c, buckets.getInt(KEY_MIN), buckets.getInt(KEY_MAX)); - - if (DEBUG) { - Log.d(TAG, experimentName + " = " + inExperiment); - } - return inExperiment; - } catch (JSONException e) { - // If the experiment name is not found in the JSON, just return false. - // There is no need to log an error, since we don't really care if an - // inactive experiment is missing from the config. - return false; - } - } - - private static List<String> getExperimentNames(Context c) throws JSONException { - // TODO: cache the array into a mapping so we don't do a loop everytime we are looking for a experiment key - final List<String> returnList = new ArrayList<>(); - final String config = Preferences.getDynamicConfigJson(c); - final JSONArray experiments = new JSONObject(config).getJSONArray(KEY_DATA); - - for (int i = 0; i < experiments.length(); i++) { - JSONObject entry = experiments.getJSONObject(i); - returnList.add(entry.getString(KEY_NAME)); - } - return returnList; - } - - @Nullable - private static JSONObject getExperiment(Context c, String experimentName) throws JSONException { - // TODO: cache the array into a mapping so we don't do a loop everytime we are looking for a experiment key - final String config = Preferences.getDynamicConfigJson(c); - final JSONArray experiments = new JSONObject(config).getJSONArray(KEY_DATA); - JSONObject experiment = null; - - for (int i = 0; i < experiments.length(); i++) { - JSONObject entry = experiments.getJSONObject(i); - if (entry.getString(KEY_NAME).equals(experimentName)) { - experiment = entry; - break; - } - } - return experiment; - } - - private static boolean isMatch(Context c, @Nullable JSONObject matchKeys) { - // If no match keys are specified, default to enabling the experiment. - if (matchKeys == null) { - return true; - } - - if (matchKeys.has(KEY_APP_ID)) { - final String packageName = c.getPackageName(); - try { - if (!packageName.matches(matchKeys.getString(KEY_APP_ID))) { - return false; - } - } catch (JSONException e) { - Log.e(TAG, "Exception matching appId", e); - } - } - - if (matchKeys.has(KEY_COUNTRY)) { - try { - final String country = Locale.getDefault().getISO3Country(); - if (!country.matches(matchKeys.getString(KEY_COUNTRY))) { - return false; - } - } catch (MissingResourceException|JSONException e) { - Log.e(TAG, "Exception matching country", e); - } - } - - if (matchKeys.has(KEY_DEVICE)) { - final String device = Build.DEVICE; - try { - if (!device.matches(matchKeys.getString(KEY_DEVICE))) { - return false; - } - } catch (JSONException e) { - Log.e(TAG, "Exception matching device", e); - } - - } - if (matchKeys.has(KEY_LANG)) { - try { - final String lang = Locale.getDefault().getISO3Language(); - if (!lang.matches(matchKeys.getString(KEY_LANG))) { - return false; - } - } catch (MissingResourceException|JSONException e) { - Log.e(TAG, "Exception matching lang", e); - } - } - if (matchKeys.has(KEY_MANUFACTURER)) { - final String manufacturer = Build.MANUFACTURER; - try { - if (!manufacturer.matches(matchKeys.getString(KEY_MANUFACTURER))) { - return false; - } - } catch (JSONException e) { - Log.e(TAG, "Exception matching manufacturer", e); - } - } - - if (matchKeys.has(KEY_VERSION)) { - try { - final String version = c.getPackageManager().getPackageInfo(c.getPackageName(), 0).versionName; - if (!version.matches(matchKeys.getString(KEY_VERSION))) { - return false; - } - } catch (NameNotFoundException|JSONException e) { - Log.e(TAG, "Exception matching version", e); - } - } - - // Default to return true if no matches failed. - return true; - } - - /** - * @return a list of all active experiments. - */ - public static List<String> getActiveExperiments(Context c) { - final List<String> returnList = new ArrayList<>(); - - final String config = Preferences.getDynamicConfigJson(c); - if (config == null) { - return returnList; - } - - try { - final JSONObject data = new JSONObject(config); - final List<String> experiments = getExperimentNames(c); - - for (int i = 0; i < experiments.size(); i++) { - final String name = experiments.get(i); - - // Check override value before reading saved JSON. - Boolean isActive = Preferences.getOverrideValue(c, name); - if (isActive == null) { - // TODO: This is inefficient because it will check all the match cases on all experiments. - isActive = isInExperiment(c, name); - } - if (isActive) { - returnList.add(name); - } - } - } catch (JSONException e) { - // Something went wrong! - } - - return returnList; - } - - /** - * Checks if a certain experiment has additional values. - * @param c ApplicationContext - * @param experimentName Name of the experiment - * @return true when experiment exists - */ - public static boolean hasExperimentValues(Context c, String experimentName) { - return getExperimentValuesFromJson(c, experimentName) != null; - } - - /** - * Returns the experiment value as a JSONObject. - * @param experimentName Name of the experiment - * @return Experiment value as String, null if experiment does not exist. - */ - @Nullable - public static JSONObject getExperimentValuesFromJson(Context c, String experimentName) { - final String config = Preferences.getDynamicConfigJson(c); - - if (config == null) { - return null; - } - - try { - final JSONObject experiment = getExperiment(c, experimentName); - if (experiment == null) { - return null; - } - return experiment.getJSONObject(KEY_VALUES); - } catch (JSONException e) { - Log.e(TAG, "Could not create JSON object from config string", e); - } - - return null; - } - - /** - * Returns a String containing the server response from a GET request - * @param url URL for GET request. - * @return Returns String from server or null when failed. - */ - @Nullable private static String readFromUrlGET(URL url) { - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setUseCaches(false); - - InputStream is = connection.getInputStream(); - InputStreamReader inputStreamReader = new InputStreamReader(is); - BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192); - String line; - StringBuilder resultContent = new StringBuilder(); - while ((line = bufferReader.readLine()) != null) { - resultContent.append(line); - } - bufferReader.close(); - - return resultContent.toString(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - /** - * Return the bucket number of the user. There are 100 possible buckets. - */ - private static int getUserBucket(Context c) { - final DeviceUuidFactory df = new DeviceUuidFactory(c); - final String uuid = df.getDeviceUuid().toString(); - - CRC32 crc = new CRC32(); - crc.update(uuid.getBytes()); - long checksum = crc.getValue(); - return (int)(checksum % 100L); - } -} |