mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-04 14:58:37 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
390
mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
vendored
Normal file
390
mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
vendored
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue